Tokens

Tokens are the smallest units of text in a Visual DataFlex program. A Visual DataFlex program is made up of tokens and separators. A separator is a blank, tab character or a new line/carriage return. Each token must be separated by one or more separators.

Read more about this in these Tokens subtopics:

  • Reserved Words

  • Numbers

  • Character Strings

  • Date Tokens

  • Comments


Reserved Words

Visual DataFlex reserved words are the tokens used as commands, pre-defined functions, pre-defined constants, pre-defined variables and compiler directives. Reserved words in Visual DataFlex are not case sensitive, so you can mix uppercase and lowercase letters in your programs.

Reserved words cannot be redefined. For example you cannot define a variable called while because while is the Visual DataFlex reserved word for a looping statement.

Refer to the Visual DataFlex Language Reference for a complete listing of commands, functions, and pre-defined tokens.


Numbers

Visual DataFlex can represent four different number types, they are; Integer, Number, Real and Hexadecimal.

Integer Tokens

Integer tokens are formed by combining numeric digits, and an optional minus sign. These represent whole numbers in the range –2147483647 to 2147483648. Numbers outside this range cannot be represented as an integer token.

Since integers are whole numbers, they cannot contain decimal places.

Examples of integer tokens are:

2800

0

-1000000

These tokens are not integers:

-2800.01 // this number contains decimal places

12345678901234567890 // this number exceeds the maximum range

Number Tokens

Number tokens are formed by combining numeric digits, a decimal point, and an optional minus sign. These represent real numbers in the range +/–99999999999999.99999999.

Examples of number tokens are:

1000.55

-0.0000004

Real Tokens

Real number tokens are used to represent numbers in a floating-point format.

A real number is formed by combining mantissa and exponent parts using the special symbol e (scientific notation) to delimit the exponent. The format of each of the mantissa and exponent parts is the same as a Visual DataFlex Number token (but the range for each is smaller). The exponent is written after the mantissa without any intervening separators.

Examples of real tokens are:

1.55e003 // 1.55 x 10 to the power of 3, or 1,550

-2.0e-002 // -2 x 10 to the power of –2 or 2/100

Visual DataFlex can represent real numbers in the range +/-1.0 times 10 to the power of +/-306. Real numbers are accurate to a precision of 15 significant digits, (i.e., the mantissa can be expressed as a number up to 15 digits following the decimal point).

Hexadecimal Tokens

Hexadecimal tokens are used to denote integer-type numbers in hexadecimal format. A hex number is formed by writing a dollar symbol ($) followed by 1 to 8 hexadecimal digits. Hex numbers must be within the range $00000000 to $FFFFFFFF.

Examples of hex tokens are:

$1F

$B200A1


Character Strings

A Visual DataFlex string token is a sequence of zero or more typeable characters, written together and enclosed by quotation marks (“ or ‘).

Visual DataFlex allows you to use either single quote (') or double quote (") characters to delimit a string. Whichever type you use must match at both ends of the string. This allows you to type a string with embedded quote marks, so long as the embedded quotes are different to those used to delimit the string.

Examples of string tokens are:

"The quick brown fox"

'jumped over the lazy dog... 3 times!'

"Frank says 'hello'."

'Susan says "Hi".'

""


Date Tokens

A Visual DataFlex date token is represented by a sequence of numbers and separators that depict the day, month and year of the specific date. The ordering of the day, month and year digits and the type of separator that is used is configurable, and transparent.

By default, Visual DataFlex represents dates in the following format: mm/dd/yyyy. Where mm represents the month (1-12), dd the day (1-31 or whatever the maximum day is for a given month) and yyyy is the year (0001 - 2200).

Visual DataFlex can be configured to use European dates in the format dd/mm/yyyy or military dates in the format yyyy.mm.dd.

Examples of date tokens are:

12/20/1990 // USA format representing December 20, 1990

03/28/1961 // USA format representing March 28, 1961


Comments

A comment is constructed by typing a double-slash (//) followed by the comment text. Every character from the double-slash to the end of the line are part of the comment.

Examples of comments are:

// This is a comment

Move 100 To iCount // This is a commented assignment statement

Comment constructs are ignored by the compiler.


Types

Data Types, Variables and Constants

When you declare a variable, you must state its type. A variable's type determines the set of values it can have and the operations that can be performed on it. Every expression and every function returns a value of a particular type.

Visual DataFlex Types fall into three categories:

  • Fundamental Types

  • Structured Types

  • Array Types

Read more in these Data Types, Variables and Constants subtopics:

  • Automatic Type Conversion

  • Declaring Variables

  • Declaring Constants


Fundamental Types

Commonly Used Types

  • Boolean

  • Date

  • Integer

  • Number

  • Real

  • RowId

  • String

  • Variant

Additional Types

  • Address

  • BigInt

  • Char

  • Currency

  • DateTime

  • Decimal

  • DWord

  • Float

  • Handle

  • Pointer

  • Short

  • Time

  • TimeSpan

  • UBigInt

  • UChar

  • UInteger

  • UShort


Address Type

Visual DataFlex Address types are used to declare variables for storing the memory address of a variable.

You can assign a value to an address variable with the AddressOf function. This command will retrieve the memory address of a named variable and return it to the Address variable.

String sName

Address aNameVar

Move 'John' to sName

Move (AddressOf(sName)) to aNameVar

The above example first declares a string variable. Next a value is assigned to the string. This allocates memory to the string. Next the memory address of the string is stored in the address variable aNameVar.

Notes: A variable's address is not allocated until a value is first assigned to it. When string variables grow in size, their address in memory may change.

Range

0 to 4294967296

Note

For more information refer to Declaring Variables and the Address command.


BigInt

BigInt Type

The BigInt type is an 8 byte signed integer.

Range

-0x8000000000000000 to 0x7fffffffffffffff

(approximately -8 * 1018 to 8 * 1018)

Note

For more information refer to Declaring Variables and the BigInt command.


Boolean

Boolean Type

The Boolean type is a simple type that is used for declaring variables for storing Boolean values. Boolean values are denoted by the predefined constants True and False. The Integer value of False is 0 and the Integer value of True is 1. For example…

Boolean bChanged

Move False to bChanged

Range

True, False

Note

For more information refer to Declaring Variables and the Boolean command.


Char

Char Type

The Char type is a one byte signed integer.

Range

-1 to 127

Note

For more information refer to Declaring Variables and the Char command.


Currency

Currency Type

The Currency type is 64-bit integer that is interpreted with its last four digits as decimals. This is more efficient than BCD representations of numbers. This data type is defined in COM and Visual BASIC.

Range

-8 * 1014 to 8 * 1014

Note

For more information refer to Declaring Variables and the Currency command.


Date

Date Type

Date is a simple type that is used to declare variables for storing Date tokens. For example…

Date dToday

Move 06/01/2020 to dToday

Range

01/01/0001 to 01/01/2500

Note

For more information refer to Declaring Variables and the Date command.

See Also

DateTime Type


DateTime Type

The DateTime type corresponds to an SQL Timestamp_Struct as defined in ODBC.

DateTime types are used for declaring variables for storing DateTime tokens.

Range

Structure, From Jan 1, 1900. A double precision number is used internally.

Note

For more information refer to Declaring Variables and the DateTime command.


Decimal

Decimal Type

Internally, the Decimal is a 17-byte number that can contain any decimal number with up to 16 digits to the left of the decimal point and 16 digits to the right of the decimal point. The external interface is as a SQLNUMERIC_STRUCT as defined in ODBC 1.0.

Range

-9999999999999999.9999999999999999 to

9999999999999999.9999999999999999

Note

For more information refer to Declaring Variables and the Decimal command.


DWord Type

DWord is a simple type that typically used to declare variables for storing Windows Registry information (to be read from or written to DWord Registry values) or for interfacing with Windows API calls.

Range

-2147483648 to 2147483647

Note

For more information refer to Declaring Variables and the DWord command.


Float

Float Type

A Float is a 32-bit IEEE floating point number.

Range

Mantissa precision 7, 10-38 to 1038

Note

For more information refer to Declaring Variables and the Float command.


Handle

Handle Type

Visual DataFlex handle types are used to declare variables for storing references to some other identifier, such as an object.

Handle hServer

Move Customer_DD to hServer

Range

-2147483648 to 2147483647

Note

For more information refer to Declaring Variables and the Handle command.


Integer

Integer Type

Integer is a simple type that is used to declare variables for storing Integer tokens. For example…

Integer iValue

Move 10 to iValue

Range

-2147483648 to 2147483647

Note

For more information refer to Declaring Variables and the Integer command.


Number

Number Type

Number is a simple type that is used to declare variables for storing Number tokens. Number variables are ideal for storing and calculating monetary values. For example…

Number nBalance

Move 100.51 to nBalance

Range

-99999999999999.99999999 to 99999999999999.99999999

Note

For more information refer to Declaring Variables and the Number command.


Pointer Type

Visual DataFlex Pointer types are used to declare variables for storing the memory address of a variable.

You can assign a value to a pointer variable with the AddressOf function. This command will retrieve the memory address of a named variable and return it to the pointer variable.

String sName

Pointer lpNameVar

Move 'John' to sName

Move (AddressOf(sName)) to lpNameVar

The above example first declares a string variable. Next a value is assigned to the string. This allocates memory to the string. Next the memory address of the string is stored in the pointer variable lpNameVar.

Notes: A variable's address is not allocated until a value is first assigned to it. When string variables grow in size, their address in memory may change.

Range

-2147483648 to 2147483647

Note

For more information refer to Declaring Variables and the Pointer command.


Real

Real Type

Real is a simple type that is used to declare variables for storing Real tokens. For example…

Real rVelocity

Move 23.00451e3 to rVelocity

Range

+/-1.0 e +/-306

Note

For more information refer to Declaring Variables and the Real command.


RowId Type

The RowId type is used to declare variables that store a unique identifier for a specific row (aka record) of a database table.

RowId variables can be used to store record identifiers and to find records identified by a RowId value.

Example

Open Customer

RowId[] riCustomers // array of RowId's

Integer i iSize

// loop through the customer table and store each row's ID

Repeat

Find GT Customer by 1

// store the RowId.

If (Found) Begin

Move (SizeOfArray(riCustomers)) to iSize

Move (GetRowID(Customer.File_Number)) to riCustomers[iSize]

End

Until (not(Found))

// Now loop through the RowId's and find the associated row.

Move (SizeOfArray(riCustomers)) to iSize

For i From 0 to (iSize – 1)

// find the customer for this row.

If (FindByRowID(Customer.File_Number, riCustomers[i])) Begin

Showln Customer.Name

End

Loop

The above example loads each row of the customer table and stores the RowId in an array of type RowId. In the second loop, the array is iterated and each stored RowId is used to locate the corresponding row in the customer table.

RowId Composition

The data that forms the RowId of a particular table is specific to the design of that table. For example in the simple case it may be composed of an integer value, or it may be a composition of an integer, a string and a date value, or it may be a number a Boolean and a date, etc.

In general, the format of the data that is stored in a RowId is internal and cannot be manipulated directly.

Manipulating RowIDs

All manipulation of RowIDs is performed using RowId functions. Using the RowId functions you can:

Retrieve a RowId

Example:

Open Customer

RowId riCustomer

Boolean bFound

Find GT Customer by 1

Move (GetRowID(Customer.File_Number)) To riCustomer

Find a Row

Example:

Move (FindByRowID(Customer.File_Number, riCustomer)) To bFound

The FindByRowID function loads the row into the customer table's buffer that corresponds with the RowId value stored in riCustomer.

Clear a RowId

Example:

Move (NullRowID()) To riCustomer

After calling the NullRowID function riCustomer contains the null RowId.

Test for a Null RowId

Example:

If (Not(IsNullRowID(riCustomer))) Begin

Move (FindByRowID(Customer.File_Number, riCustomer)) To bFound

End

Test for Equality

Example:

RowId riBestCustomer

//..

Move (GetRowID(Customer.File_Number)) To riCustomer

If (IsSameRowID(riCustomer, riBestCustomer)) Begin

Send Stop_Box ("Best customer is:" * Customer.Name) ""

End

Serialize a RowId

Example:

String sRowID

Move (SerializeRowID(riCustomer)) To sRowID

De-serialize a RowId

Example:

Move (DeSerializeRowID(sRowID)) To riCustomer

Web applications and web services use serializing and de-serializing to transform RowIDs into and out of a format that can be used in URLs, Web pages and Web Services.

For more information regarding RowIDs refer to: The RowId command and the RowId Helper Functions.

For information regarding RowIDs and data dictionaries, refer to the DataDictionary class.


Short

Short Type

The Short type is a two byte signed integer.

Range

-32768 to 32767

Note

For more information refer to Declaring Variables and the Short command.


String

String Type

The String type is used to declare variables for storing Strings. For example…

String sName

Move "John" to sName

Visual DataFlex allocates one byte of storage per character contained in the string. The size of a string variable is dynamically allocated at runtime as the string value changes in length up to a maximum of 64 KB (65536) characters. However, you can use the Set_Argument_Size command to increase the maximum to 2GB.

Notes

Global string variables can only contain a maximum of 80 characters by default and an absolute maximum of 255 characters. The Set_Argument_Size command has no effect on global strings. See Global Variables for more information.

For more information regarding string variables refer to Declaring Variables and the String command.


Time

Time Type

The Time type corresponds to an SQL Time_Struct as defined in ODBC.

Struct Time

uShort Hour

uShort Minute

uShort Second

End_Struct

Time types are used for declaring variables for storing Time tokens.

Range

00:00:00 to 23:59:59

Note

For more information refer to Declaring Variables and the Time command.


TimeSpan

TimeSpan Type

The TimeSpan type has the same internal structure as a DateTime. Only Days, Hours, Minutes, and Seconds are stored.

TimeSpan types are used for declaring variable for storing the difference of two DateTime values, i.e., the subtraction of one DateTime value from another.

Range

65535 days.

Note

For more information refer to Declaring Variables and the TimeSpan command.


UBigInt

UBigInt Type

The UBigInt type is an 8 byte unsigned integer.

Range

0x0 to 0xFFFFFFFFFFFFFFFF

(approximately 0 to 1.6 * 1019)

Note

For more information refer to Declaring Variables and the UBigInt command.


UChar

UChar Type

The UChar type is a one byte unsigned integer.

Range

0 to 255

Note

For more information refer to Declaring Variables and the UChar command.


UInteger

UInteger Type

The UInteger type is a 4 byte unsigned integer.

Range

0 to 4GB minus 1

Note

For more information refer to Declaring Variables and the UInteger command.


UShort

UShort Type

The UShort type is a two byte unsigned integer.

Range

0 to 65535

Note

For more information refer to Declaring Variables and the UShort command.


Variants

Variant Type

Variant variables are capable of storing values that change type dynamically. Variables of any other type (Integer, String, Date, etc) are statically bound to that type but a variable of the Variant type can assume values of differing types at runtime.

You would use a Variant in situations where the actual type of your data is unknown at compile-time. This is a common situation when writing applications that use COM objects.

Variants have the following characteristics:

  • Variants can contain Integer, Number, Real, Boolean, Date, Time, TimeSpan, DataTime, String, RowId, Handle and IDispatch pointer values.

  • The special Variant value VT_EMPTY is used to indicate that a Variant has not yet been assigned any value.

  • Variants can be combined with other Variants and Integer, String, Boolean etc. values in an expression. The VDF expression evaluator will automatically perform the necessary type conversions.

  • When a Variant contains a reference to a COM Object (IDispatch*), then it automatically performs reference counting for the referenced object.

Note

For more information refer to Declaring Variables the Variant command and Using Variant Data Types with COM.


Struct Type

Structured Types

A struct represents a set of elements. Each element is called a member. Struct members can be of any valid DataFlex type as well as other struct types and array types.

Read more about this in these Structured Types subtopics:

  • Struct Declaration

  • Struct Variables

  • Struct Variable Assignments

  • Struct Properties


Struct Declaration

A struct type declaration defines a particular set of elements, called struct members, and associates it with a new type identifier. Once a struct type is declared you may declare and use variables of that type.

You declare a struct type using the Struct and End_Struct keywords. For example…

Struct tUSAddress

String sStreet

String sCity

String sState

Integer iZipCode

End_Struct

Struct tPurchaseOrder

Integer iOrderNumber

Date dOrderDate

tUSAddress shippingAddress

tUSAddress billingAddress

...

End_Struct

In the above example the tUSAddress struct contains four members: three strings named sStreet, sCity and sState, and an integer named iZipCode.

Struct type names must be unique throughout the program. Struct member names are local to the struct type and need only be unique within the type itself. The Visual DataFlex naming convention is to prefix struct type names with a lowercase "t".

Struct members may be of any valid data type, including other struct types and array types.

Struct tMyStruct

String sName

Integer[] iValues

End_Struct

Struct tMyOtherStruct

Integer iCode

String sValue

tMyStruct nestedStructMember

End_Struct

Structs may be nested by specifying struct type members as long as the member struct type has been declared before this struct declaration. Struct members of the declaring struct (i.e. recursive struct declarations) are illegal. However, dynamic arrays of the declaring struct are allowed. This is referred to as indirect-recursive struct declarations.

Struct tTreeNode

...

tTreeNode[] children

End_Struct

Struct members of array type Static, dynamic and jagged array type members are allowed. The size of each dimension in static array type members must be a constant computed at compile time.

See Also

SizeOfType


Struct Variables

A struct type is instantiated just like any other Visual DataFlex type.

tUSAddress myAddress

The above creates a variable named myAddress of the struct type tUSAddress.

Struct types can also be used as parameter and function return types as well as properties. For example…

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property tUSAddress pCurrentAddress

End_Procedure

Procedure SaveAddress tUSAddress myBillingAddress

//...

End_Procedure

Function CreateAddress Returns tUSAddress

tUSAddress myAddress

//...

Function_Return myAddress

End_Function

Function TransformAddresses tUSAddress[] adresses Returns tUSAddress[]

tUSAddress[] returnAddresses

//...

Function_Return returnAddresses

End_Function

End_Class

Member Access

Members of a struct variable are accessed using the member access operator (the dot operator).

Integer iZipCode

Move "Miami Beach" to myAddress.sCity

Move myAddress.iZipCode to iZipCode

Note

For more information about structs refer to the Struct command.

See Also

Declaring Variables


Struct Variable Assignments

A variable of a particular struct type can be copied to another variable of the same struct type.

tUSAddress myShippingAddress

tUSAddress myBillingAddress

...

Move myShippingAddress to myBillingAddress

The above copies the variable myShippingAddress to the myBillingAddress variable using a deep memberwise copy operation. Each member in turn of the struct type is copied from the myShippingAddress variable to the myBillingAddress variable. The above code is functionally equavalent to the following…

Move myShippingAddress.sStreet to myBillingAddress.sStreet

Move myShippingAddress.sCity to myBillingAddress.sCity

Move myShippingAddress.sState to myBillingAddress.sState

Move myShippingAddress.iZipCode to myBillingAddress.iZipCode

Struct variable assignments of different struct types, as well as struct variable assignments to/from a non-struct type, are illegal and will result in a run-time error.


Struct Properties

Special consideration needs to be given for accessing data that is stored in class properties that are of struct type. The Get and Set commands that are used for accessing a property's data can only be used to get and set the entire struct. This works just like a struct variable assignment.

To read or write individual members of a struct property, it is necessary to get the property to a local struct variable of the same type. You would then manipulate the data of this local struct and write back the changes by setting the struct property once again.

Example…

struct tNameAndAddress

string sName

string sAddress

end_struct

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

// Declare a struct property

Property tNameAndAddress pContact

End_Procedure

Procedure StoreName String sName

tNameAndAddress contact // create a local struct variable

// get the struct property to the variable.

Get pContact to contact

// show the current name.

Send Info_Box ("The current name is: " + contact.sName)

// change the name.

Move sName to contact.sName

// Save the change back to the struct property.

Set pContact to contact

End_Procedure

End_Class

Accessing struct properties utilizes the copy-on-write concept, which means that getting/setting struct properties do not impose a full copy operation itself. If you are only reading the struct members in a procedure, then the copy to a local struct variable is very fast. Visual DataFlex will only copy a reference to the struct until the first time that you perform a write to this copy. At the time of the first write a full copy will be made. Thus if your code never writes to the copy, then it will be very efficient.

For more information refer to Setting Property Values and Reading Property Values.


Array Type

Array Types

An array represents an indexed collection of elements of the same type (the base type). Array types can be based on any valid DataFlex type, including struct types.

Read more about this in these Array Types subtopics:

  • Single-dimensional Arrays

  • Multidimensional Arrays

  • Array Variable Assignments

  • Array Parameters

  • Array Properties

  • Static vs. Dynamic vs. Jagged Arrays

  • Array Functions


Single-Dimensional Arrays

Array types are instantiated similarly to any other type. Array declarations use brackets [] to specify each array dimension.

Integer[] iValues

The above creates an array variable named iValues, where each element is of type Integer. For more information see Array Declarations.

Individual array elements are accessed by typing the name of the variable followed by the array indexer for the element number you wish to access in square brackets. Array dimensions are 0-based i.e. iValues[3] denotes the 4th integer in iValues. For example…

Integer[] iValues

Integer iCount

For iCount from 0 to 9

Writeln iValues[iCount]

Loop

Arrays may be defined to be dynamic or static. Normally you will create dynamic arrays.

Dynamic single-dimensional arrays

Arrays without a fixed size are called Dynamic arrays.

String[] sNames

The above creates a dynamic single-dimensional array variable named sNames, containing an undefined number of elements of type String. For more information see Array Declarations.

Dynamic arrays grow as needed when individual elements are accessed.

Dynamic arrays can be used for both local/global variable declarations as well as struct members and properties.

Static single-dimensional arrays

Arrays with a specified dimension size, are called Static Arrays. Static Arrays have a fixed number of elements, which cannot later be changed.

Integer[10] iValues

The above creates a static single-dimensional array variable named iValues, containing 10 elements of type Integer. For more information see Array Declarations.

The specified size in a static array variable declaration does not need to be a constant, it can also be an expression computed at run-time. Except for when used to declare a struct member or a global (non-local) variable, where the size must be a constant computed at compile-time.

Array Functions

A number of array functions for handling many typical array-specific tasks, such as searching for array elements, removing or adding new blank elements to dynamic arrays.

See Also

Static vs. Dynamic vs. Jagged Arrays


Multidimensional Arrays

Multidimensional arrays are specified using multiple pairs of brackets, each pair specifying one dimension.

Integer[][] iScores

The above creates a two-dimensional array variable named iScores.

Individual array elements of a multidimensional array are accessed by typing the name of the variable followed by the indexer for each dimension in pairs of square brackets. Array dimensions are 0-based i.e. iValues[3][0] denotes the first element on the 4th row of a two-dimensional array. For example…

Integer[][] iScores

Integer iRow

Integer iCol

For iRow from 0 to 9

For iCol from 0 to 3

Move 1234 to iValues[iRow][iCol]

Loop

Loop

Dynamic multidimensional arrays

Dynamic multidimensional arrays are declared by omitting the size of the most significant (leftmost) dimension, while specifying the size of the remaining dimensions.

Integer[][3] iColors

The above creates a two-dimensional dynamic array variable named iColors, containing an undefined number of elements of type Integer. Conceptually this represents a rectangular array with an undefined number of rows, each of 3 columns.

[0][0] [0][1] [0][2]

[1][0] [1][1] [1][2]

[2][0] [2][1] [2][2]

[…][0] […][1] […][2]

[n][0] [n][1] [n][2]

The most significant (leftmost) dimension of a dynamic multidimensional array grows as needed when individual elements are accessed. The remaining dimension have a fixed size, and cannot later be changed.

The specified size of each dimension does not need to be a constant, it can also be an expression computed at run-time. Except for when used to declare a struct member or a global(non-local) variable, where the size must be a constant computed at compile-time.

Static multidimensional arrays

Static multidimensional arrays are declared by specifying the size of all dimensions.

Integer[2][3] iScores

The above creates a two-dimensional static array variable named iScores, containing 2x3 number of elements of type Integer. Conceptually divided into 2 rows, each of 3 columns.

[0][0] [0][1] [0][2]

[1][0] [1][1] [1][2]

The specified size of each dimension does not need to be a constant, it can also be an expression computed at run-time. Except for when used to declare a struct member or a global(non-local) variable, where the size must be a constant computed at compile-time.

Jagged Arrays

You can create dynamic multidimensional arrays that are non-rectangular. Jagged arrays are conceptually and syntactically similar to dynamic multidimensional arrays, where all dimensions are dynamic. For example…

Integer[][] iValues

The above declares a two-dimensional jagged array variable named iValues, containing an undefined number of elements of type Integer. Conceptually this represents a structure containing an undefined number of rows, each row containing an undefined number of columns. Where each row can have different number of columns.

In contrast with regular multidimensional arrays, which are rectangular, jagged arrays are non-rectangular. For example, it's possible to have 1 column in the first row, and three columns in the second row.

[0][0]

[1][0] [1][1] [1][2]

[2][0] [2][1]

[3][0]

[4][0] [4][1] [4][2] [4][3]

Array Functions

A number of array functions for handling many typical array-specific tasks, such as searching for array elements, removing or adding new blank elements to dynamic arrays.

See Also

Static vs. Dynamic vs. Jagged Arrays


Array Variable Assignments

A variable of a particular array type can be copied to another variable of the same array type. For example…

Integer[] iValues

Integer[] iMoreValues

Move iValues to iMoreValues

The above copies the variable iValues to the variable iMoreValues using a deep memberwise copy operation. Each element in turn is copied from the iValues variable to the iMoreValues. The following code would be functionally equivalent to the first example…

Integer[] iValues

Integer[] iMoreValues

Integer iCount

Integer iSize

Move (SizeOfArray(iValues)) to iSize

For iCount From 0 to (iSize-1)

Move iValues[iCount] to iMoreValues[iCount]

Loop

Array variable assignments of incompatible types, as well as array variable assignment to/from a non-array type, are illegal, and will result in a run-time error.

Array variables are assignment compatible only if they are of the same type and number of dimensions, and:

  • If the destination variable is a static array: The size of each dimension in the source and destination variable are the same.

  • If the destination variable is a dynamic array: The size of each dimension except the most significant dimension are the same.

Dynamic arrays adopt the size of the source array in the assignment. And jagged arrays adopt the size of each dimension of the source array in the assignment.

See Also

Array Types


Array Parameters

You can declare array properties, and arrays can also be used as parameter and function return types.

Array paremeters must always be declared without specifying the size of each dimension, as it’s only a formal parameter declaration. The actual parameter determines the actual size of each dimension.

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property Integer[] piValues

Property tUSAddress[] pCurrentAddress

End_Procedure

Procedure ProcessValues Integer[] values

//...

End_Procedure

Function CreateValues Returns Integer[]

Integer[] iValues

//...

Function_Return iValues

End_Function

//Assuming a struct type tUSAddress has been declared somewhere else

Function TransformAddresses tUSAddress[] adresses Returns tUSAddress[]

tUSAddress[] returnAddresses

//...

Function_Return returnAddresses

End_Function

End_Class

See Also

Declaring Variables


Array Properties

Special consideration needs to be given for accessing data that is stored in array properties. The Get and Set commands that are used for accessing a property's data can only be used to get and set the entire array. This works just like an array variable assignment.

To read or write individual array property elements, it is necessary to get the property to a local array variable of the same type. You would then manipulate the data of this local array and write back the changes by setting the array property once again.

Example…

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

// Declare an array property

Property string[] psNames

End_Procedure

Procedure StoreName String sNewName

string[] sNames // create a local array variable

integer iElementCount

// get the array property to the variable.

Get psNames to sNames

// Add the new name.

Move (SizeOfArray(sNames)) to iElementCount

Move sNewName to sNames[iElementCount]

// Save the change back to the array property.

Set psNames to sNames

End_Procedure

End_Class

Accessing array properties utilizes the copy-on-write concept, which means that getting/setting array properties do not impose a full copy operation itself. If you are only reading the array elements in a procedure, then the copy to a local array variable is very fast. Visual DataFlex will only copy a reference to the array until the first time that you perform a write to this copy. At the time of the first write a full copy will be made. Thus if your process never writes to the copy, it will be very efficient.

For more information refer to Setting Property Values and Reading Property Values.


Static vs. Dynamic vs. Jagged Arrays

You may wonder, why all these different names for arrays? Syntactically it’s all very similar, the only difference is whether the size of a dimension is fixed or not, or is it?

The truth is that in most cases it doesn’t really matter whether you use a static, dynamic or jagged array. Therefore the simple guideline is to always prefer to leave the size of all dimensions unspecified. It’s simple, easy to remember and easy to use. It also gives you the most flexibility, as the size of each dimension will grow as needed. Technically speaking, this means that you would favor dynamic arrays over static arrays, and jagged arrays over multidimensional dynamic arrays.

If you should always prefer to leave the size of all dimensions unspecified, then what is the point of letting you specify the size of a dimension? While it’s true that static, dynamic and jagged arrays are syntactically very similar, the implementation strategies are very different. The technical implementation differences is of very little or no concern in most cases, but in some very specific cases it does matter. This section will try to describe these technical implementation differences, and when the differences matter.

When to use Static Array instead of Dynamic Array

Static arrays are useful when you need compatibility with C style arrays, and when you need to allocate a fixed size array directly embedded in a struct, again mostly for C compatibility.

If you're not sure whether you need C style array compatibility, then there's no reason to use a static array. You only need to use a static array if you really need binary compatibility with a C style static array. For example, if you're declaring a struct that will be used together with an external DLL function, and the struct member is declared as a static array in C. Again, unless you're directly translating a C struct, there's no need to use a static array member. Visual DataFlex fully supports dynamic array members in a struct. Static arrays are only needed when you require binary compatibility with C style arrays.

When to use Rectangular Multidimensional Arrays instead of Jagged Arrays

Rectangular multidimensional arrays are useful when you need compatibility with C style multidimensional arrays. Jagged arrays are not binary compatible with C, so if you really need binary compatibility with C style multidimensional arrays, you must use rectangular multidimensional arrays instead of jagged arrays.

If you know that the size of each dimension, except possibly the first one, is always fixed, then it might be more convenient to use rectangular multidimensional arrays. Otherwise it might be easier to use a jagged array instead.

Using a jagged array instead of a rectangular multidimensional array has the advantage that you can vary the number of columns for each row, and that each row will by default have 0 columns. This can lower memory usage if there are many rows where not all columns are used. The downside is that since each row will by default have 0 columns, before getting the value of a particular column, you must first establish that the column you want to access exist, or a run-time error may occur. Thus it may in some cases be more convenient and/or easier to use a rectangular array.

If you're not sure whether your array will be rectangular in nature, then use a jagged array. Jagged arrays always gives you the most flexibility, all dimensions are dynamic and will automatically resize as needed.

Structural difference of Static Array vs. Dynamic Array

Static array members of a struct take up the exact space needed for the array directly embedded in the struct. For example, a struct type containing a member Integer[5], will take up (5*SizeOfType(Integer)) bytes directly in the struct. So, if you have a struct containing only one member which is a static array, the size of that struct type is the size of that static array.

Dynamic array members of a struct always only take up the size of a pointer (normally 4 bytes). No matter how many elements there are in the array, the number of bytes used by that particular array member in the struct is always the same. For example, if you have a struct containing only one member which is a dynamic array, the size of that struct type is always 4 bytes.

It's said that static array members of a struct are directly embedded in the struct, whereas dynamic array members of a struct are only indirectly embedded in the struct.

In most cases this difference is of little or no importance. The syntax for accessing static/dynamic array members of a struct is the same. The structural difference may be important for binary compatibility with C style arrays though. Dynamic arrays, as opposed to static arrays, can also be used to form indirect-recursive struct declarations.

Structural difference of Rectangular Multidimensional Arrays vs. Jagged Arrays

Jagged arrays are technically and structurally just dynamic single-dimensional arrays of dynamic single-dimensional array type. A jagged array is simply put, an array of array type.

Syntactically, jagged arrays and multidimensional arrays are very similar. Jagged arrays are just as easy to use as rectangular multidimensional arrays.

Since jagged arrays are structurally dynamic arrays, jagged array members of a struct are only indirectly embedded in the struct. Jagged arrays can therefore be used to form indirect-recursive struct declarations.


Automatic Type Conversion

Visual DataFlex supports automatic type conversion among variables and tokens of most of the Visual DataFlex fundamental types (Integer, Number, Real, etc.). Examples of Visual DataFlex type conversion are:

Integer iTest

Move "123" To iTest

In the above example an integer variable is declared then it is assigned a string token "123". The result is that the integer 123 is stored into the integer variable. However, the following example will generate a runtime error.

Integer iTest

Move "XX123" To iTest

The reason for the error is that Visual DataFlex is unable to interpret the string "XX123" as an integer.

Further examples of automatic type conversion are:

Number nTest

Integer iTest

String sTest

Date dTest

Move 2.001e3 To nTest // 2001.0 is stored in nTest

Move nTest To iTest // 2001 is stored in iTest

Move 01/01/2001 To dTest

Move dTest To sTest // "01/01/2001 is stored in sTest


Declaring Variables

A variable is an identifier that references some value that can be changed. A variable declaration is the point in your program where you declare a new variable and designate its type.

Simple Declarations

Simple variables are declared using the following syntax:

{type} {identifier}

Where:

  • {type} is one of the Visual DataFlex fundamental data types (Integer, Date, String, Boolean, Variant, RowId, etc.) or a struct type, and,

  • {identifier} is the name that you have assigned to the new variable. Note that you cannot use a Visual DataFlex reserved word as a variable name.

A Visual DataFlex variable declaration can occur anywhere within a program. However, it is good programming practice to declare all global variables near the top of your program and all local variables near the top of their respective Procedure or Function declarations.

Examples of variable declarations are:

Integer iNew // declares a new Integer variable called iNew

Number nNew // declares a new Number variable called nNew

Real rNew // declares a new Real variable called rNew

String sNew // declares a new String variable called sNew

Date dNew // declares a new Date variable called dNew

Variant vNew // declares a new Variant called vNew

RowId riCustomer // declares a new RowId variable called riCustomer

tContact myContact // declares a new tContact struct type variable called myContact

// (assuming the tContact struct type has been declared)

Multiple variables of the same type may be named on a single command line using the following syntax:

{type} {identifier1} {identifier2 … indentifierN}

Examples of declaring multiple variables on a single command line are:

Integer iNew iOld

String sHot sMedium sCold

Each declared variable is initialized to its null state (i.e., Integers, Numbers and Reals are initialized to 0 (zero)); Strings and dates are initialized to "" (the null string), and Variants are initialized to VT_EMPTY. Your program cannot use any variable until the line of code that contains its declaration is executed.

Array Declarations

Array variables are declared using the following syntax:

{base-type}{dimension-list} {identifier} […{identifier}]

Where:

  • {base type} is the type of data stored in each array element. This can be any one of the Visual DataFlex fundamental types (Integer, Number, Real, Date etc.) or a struct type.

  • {dimension-list} specifies the number of dimensions and the maximum size of each dimension. Each dimension is specified by a pair of square brackets i.e. []. The maximum size of each dimension is specified by an integer value between the brackets e.g. [10].

Dynamic arrays are declared by omitting to specify the size of the first dimension. e.g. Integer[][4] iNames.

Jagged arrays are declared by omitting to specify the size of all dimensions. e.g. Integer[][] iNames.

  • {identifier} is the name of the new array variable. Note that you cannot use a Visual DataFlex reserved word as a variable name.

Examples of declaring arrays are:

Integer[][] iChessboard // Declares a 2-dimensional integer array called iChessboard

String[] sNames // Declares a single dimensional array called sNames

Struct tContact

String Name

String Street

String City

String ZipCode

String Phone

End_Struct

tContact[] Contacts // Declares a single dimensional array of type tContact

As with fundamental types, the elements in a declared static array are initialized to their null state. e.g. integer array elements are initialized to 0, strings to "", etc.

For more information refer to Array Types.


Local Variables

Variables declared within a Procedure or Function are called Local variables. A local variable's scope is only inside the Procedure or Function in which it is declared. A variable declaration is deemed to be within a Procedure or Function if the declaration occurs between a Procedure .. End_Procedure or Function .. End_Function command pair. Examples of local variable declarations are:

Procedure DoNothing

Integer iNew

String sTest

…

// code exists here that uses the local variables

End_Procedure

…

// Code outside the procedure cannot access the local variables

The point of local variables is that they cannot be accessed or changed outside of the procedure or function in which they are declared. This means that they can be used inside the procedure with the assurance that no code outside the procedure can corrupt its value.


Global Variables

Variables declared outside procedures and functions are called Global variables. A global variable's scope covers the entire program from the point at which it is declared.

The disadvantage of global variables is that the programmer has to carefully control their use within a large program to ensure that their values will not change unexpectedly before they are used. Using a global variable within a procedure or function ruin the function's portability (e.g., you cannot copy the procedure or function into another program without also copying the variable declaration at some other point in the program). You must also simulate the initial setting of the global variable in your new program.

Visual DataFlex variable naming convention suggests that you prefix all of your global variables with a "g". This will help you to easily identify global variables within your program. It is also recommended to use the global_variable command to make the declaration unambiguous.

Examples of global variable declarations are:

Global_Variable Integer giCount

Global_Variable String gsCommandLine

Setting the Maximum Length of a Global String

In Visual DataFlex global strings have a default length of 80 characters. This can be increased to an absolute maximum of 255 characters when the variable is first declared. The syntax for defining a long global string is:

Global_Variable String {variable-name} {maximum-length}

where {variable-name} is the name of the global string variable that is being declared; and {maximum-length} is the maximum number of characters that the global string can contain. The maximum length must be between 1-255.

An example of declaring a long global string is:

Global_Variable String gsCommandLine 255

Notes

Local string variables have a default maximum length of the number of characters specified by the current argument size. This length can be increased by using the Set_Argument_Size command.


Database Tables

A database table is declared within your Visual DataFlex application using the Declare_DataFile command. The syntax of the Declare_DataFile command is:

Declare_DataFile {tablename}

Where:

{tablename} is the name of the table that you are declaring.

The Visual DataFlex Open command declares a table then opens it in preparation for input/output operations. Since you would normally need to open any table before you use it, this command can be used as a substitute for Declare_DataFile. Examples of table declarations are:

Declare_DataFile Customer // Declares a customer table

Open Invt // Declares an inventory table and opens it

Notes

Before you can declare and use a table within your program you must connect to a database that contains that table. Refer to the Database Builder documentation for information about creating tables and connecting to databases.


Declaring Constants

The declaration of a constant corresponds to the declaration of a read-only variable. Constants can be used exactly like variables, except that they cannot be modified.

Constants are declared using the following syntax:

Define {Identifier} for {Token}

Where:

  • {Identifier} is the name assigned to the new constant and

  • {Token} is the token that the named constant will represent. You cannot use a Visual DataFlex reserved word to name a constant. The token that you use can be any String, Number or Date token.

Examples of constant declarations are:

Define C_PI for 3.14156

Define C_MaxLines for 100

Define C_CompanyName for "Data Access Corporation"

Visual DataFlex naming convention suggests that you prefix your constant names with a "C_". This will help you to distinguish constants from variable names within your program.


Enumerated Constants

Visual DataFlex provides the Enum_List command for declaring a sequence of integer constants of increasing value. Enumerated constants are declared using the following syntax:

enum_list

define {Identifier}

define {Identifier}

define {Identifier}

…

end_enum_list

Where:

  • {Identifier} is the name assigned to the new integer constant. By default, the first identifier will be assigned the integer constant 0, the second 1, then 2 and so on.

An example declaration of enumerated constants follows:

enum_list

define C_NoError // this symbol = 0

define C_MinorError // = 1

define C_Error // = 2

define C_CriticalError // = 3

end_enum_list

You can skip a number in the default sequence by introducing a for clause in the define. For example:

enum_list

define C_NoError // = 0

define C_SysError1 for 1000 // = 1000

define C_SysError2 // = 1001

define C_SysError3 // = 1002

end_enum_list


Expressions

Expressions are made up of operators and operands. Most Visual DataFlex operators are binary; they take two operands. The rest are unary and take only one operand. Binary operators are placed between the two operands; for example (X + Y). A unary operator always precedes its operand; for example (–X).

  • Expression Syntax

  • Typecasting of Expressions


Expression Syntax

Expression Body Values

Expressions are written by enclosing an expression body inside parentheses (). The following table lists the valid expression body values:

Expression Body | Example
Operand | X.
Unary operator followed by an operand | –X.
Binary operator between two operands | X / Y.

Operands

An operand can be any of the following items:

Operand | Example
Number | 100
String | "Hello"
Date | 01/01/2000
Constant | C_Maximum
Variable | iCount
Function call | Length("Hello")
Expression Body | X + Y
Expression | (X + Y)

Operators

Operators are categorized into several groups depending upon the type of expression. The table below lists all the possible operators:

Binary Arithmetic Operators | Operation
+ | Addition
- | Subtraction
* | Multiplication
/ | Division
^ | Power of
Max | Maximum
Min | Minimum
Unary Arithmetic Operator | Operation
- | Sign negation
Relational Operators | Operation
= | Equal
<> | Not equal
< | Less than
> | Greater than
<= | Less than or equal to
>= | Greater than or equal to
Contains | Member of
Matches | String compare with wildcards
Boolean Operators | Operation
Not | negation
And | logical and
Or | logical or
String Operators | Operation
+ | Concatenation
- | Trim "outer" spaces and concatenate
* | Concatenate with a single space between arguments
Bitwise Operators | Operation
Iand | Bitwise and
Ior | Bitwise or

Examples

Here are some examples of expressions:

While ((iTime < 12) and not((sDay = "Sat") or (sDay = "Sun")))

//…

Loop

This is a Boolean expression that would return True if the variables iTime and sDay were set to a weekday before 12pm.

Move (sLastName + ', ' + sFirstName + ' ' + sInitial) To sFullName

This is a string expression that concatenates two string variables with some string constants.

string sFirst sLast sName

move " Joe " to sFirst

move " Smith " to sLast

move (sFirst - sLast) to sName

This is a string expression that concatenates two string variables using the - operator, which trims all trailing spaces from the first argument and all leading spaces from the second argument, resulting in " JoeSmith " stored in sName.

string sFirst sLast sName

move " Joe " to sFirst

move " Smith " to sLast

move (sFirst * sLast) to sName

This is a string expression that concatenates two string variables using the * operator, which trims all trailing spaces from the first argument and all leading spaces from the second argument, leaving only a single space between the two strings, resulting in " Joe Smith " stored in sName.

Boolean Expressions

Boolean expressions are evaluated to a result of True or False. In Visual DataFlex any non-zero integer value can represent the Boolean value of True. The integer 0 represents the Boolean value of False.

True <> 0

False = 0

Expressions are evaluated left to right. Visual DataFlex performs short circuit evaluation of Boolean expressions. This means that if the outcome of the entire Boolean expression can be determined by only evaluating part of it then the outcome is returned without evaluating the rest of the expression. This can occur with Boolean and and or operators as demonstrated in the following examples:

If ((iValueX = 1) or (iValueY = 2)) Begin…

If ((iValueX = 1) and (iValueY = 2)) Begin…

In the first example if (iValueX = 1) is true then (iValueY = 2) is not evaluated because regardless of the outcome the entire expression will still evaluate to true. The same is true for the second example if (iValueX = 1) is False.

You most need to be aware of short circuit evaluation when your expression contains a function call as in the following example:

If ((iValueX = 1) or (GetNextCharacter() = 'X')) Begin…

In this example the function GetNextCharacter is never executed if (iValueX = 1) is True. If it is important that the GetNextCharacter function is executed, then you should re-write the expression in the following way:

Move (GetNextCharacter()) To sCharacter

If ((iValueX = 1) or (sCharacter = 'X')) Begin…

Rules of Operator Precedence

Special Note: Visual DataFlex expressions do not follow any rules of precedence among operators. You must use parentheses to force precedence. For example:

(1 + 2 * 4)

Should be written:

(1 + (2 * 4))

If you want the 2 * 4 part to be evaluated before adding one. Visual DataFlex would evaluate the first example as 12 and the second as 9.


Typecasting of Expressions

Expressions have data types the same as variables. Expressions are automatically typecast according to the highest-order data type represented among the components of the expression.

The following table lists the expression types from highest to lowest order:

Expression Typecasting, Highest to Lowest Order

  • Real

  • Date

  • Number

  • Integer

  • String

  • Boolean

Therefore, if an expression contains both Number and Integer components, the value of the expression will be Number. For example:

(5.0 / 2)

This expression will return the Number value 2.5 because one of the components (5.0) is written as number. However:

(5 / 2)

This expression will return the Integer 2 because both components are written as integers. A more explicit way to return a Number answer of 2.5 would be:

(Number(5) / 2)

In this example we are explicitly typecasting the '5' component using the Number function. However the next example will still yield an answer of 2.0:

(Number(5 / 2))

The reason is because the sub-expression (5 / 2) is evaluated to an Integer result of 2 before the Number function is applied


Statements

Statements describe the actions that can be executed as part of a program's algorithm.

  • Simple Statements

  • Conditional Statements

  • Looping Statements


Simple Statements

A simple statement is a statement that doesn't contain any other statements. Each line of code in your program can contain only one simple statement. The end of line denotes the end of the statement.

Assignment Statements

Assignment statements are used to change the value of a variable. The syntax of an assignment statement is:

Move {expression} To {Variable}

Some examples of assignment statements are:

Move ("Harry") To sName

Move (iX + iY) To iTotal

Move (iX > 100) To bFinished

Move (tan(.70711)) To nTangent

Move (sName) To Customer.Name

If the {expression} part of the assignment statement is a single constant or a variable then you may omit the parentheses (). For example the first and last assignment statements in the previous example could be re-written:

Move "Harry" To sName

Move sName To Customer.Name

Note: Visual DataFlex supports additional specialized assignment commands (Increment, Decrement, Add, Subtract). Refer to the Visual DataFlex Language Reference for more information about these commands.

Continuing a Statement on the Next Line

The end of the line character generally marks the end of the simple statement, however long statements can be continued on the following line by placing a semi-colon (;) character at the end of the first line.

You can continue a statement in this way over several contiguous lines until the maximum line length limitation is reached. Examples of a simple statement continued over more than one line are:

Function TagName string sDrive string sDir string sExt ;

returns string

If (sTagname="") ;

Get TagName "c:" sDfltDir sDfltExt to sTagName

Else ;

Move "" to sTagName

Move ("This is quite a long message " + ;

"so I will break it up over " + ;

"three lines!") To sMessage

Notes

You cannot break any single token over two lines (e.g., in the above example the message could not have been expressed as a single string and broken over several lines). Instead it was written as three strings that are appended together by the append string operator (+).


Conditional Statements

A conditional statement selects some other statement for execution depending upon the outcome of one or more Boolean expressions. Refer to Boolean Expressions for further information.

Read more in these Conditional Statements subtopics:

  • Compound Statements

  • If

  • Case


Compound Statements

A Begin .. End Block is used to mark the beginning and end of a compound statement. A compound statement is a sequence of statements that are to be executed in the same sequence that they are written. The syntax of a compound statement is:

Begin

{statement 1}

{statement 2}

…

{statement n}

End

Where:

  • {statement} can be any valid Visual DataFlex statement, including another Block.

A compound statement can be considered as one single statement comprised of many parts. This is useful when the syntax of some other statement conditionally executes one secondary statement. If you need to execute more than one statement you can use Begin .. End to create a single compound statement.

An example of a compound statement is:

Begin

Move (0) To iStart

Move (100) To iEnd

Move (2) To iStep

End


If

The syntax for an if statement is:

If {Boolean-expression} {true-statement}

Else {false-statement}

Where:

  • {Boolean-expression} is some expression that will return a True or False value;

  • {true-statement} is the statement that will execute if True is returned by the Boolean expression;

  • {false-statement} is the statement that will execute if False is returned by the Boolean expression.

The else part of the if statement is optional. If it is not present, then execution will continue at the next statement in the program when the Boolean expression is False (without executing the {true-statement}).

Some examples of if statements are:

If (iCount > 10) Move ("greater than 10") To sMessage

Else Move ("less or equal to 10") To sMessage

If ((iValue – 1) < 1000) Begin

Move (iValue) To Sales.Small

Move ("sub 1000") To Sales.Note

End

Else Begin

Move (iValue) To Sales.Large

Move ("over 1000") To Sales.Note

End

If (bSmoking) Move ("Somebody stop me!") To sMessage


Case

The syntax for a Case statement is:

Case Begin

Case {Boolean-expression1}

{statement1}

Case Break

Case {Boolean-expression2}

{statement2}

Case Break

Case {Boolean-expression3}

{statement3}

Case Break

…

Case Else

{else-statement}

Case End

If Boolean-expression1 is True then statement1 is executed, if Boolean-expression2 is True then statement2 is executed and if Boolean-expression3 is True then statement3 is executed. If none of the Boolean-expressions are true then the else-statement is executed.

The Case statement will allow any number of Boolean-expressions and statements to be executed. The Case Else and {else-statement} are optional. If it is not present then execution will continue at the next statement in the program when all the Boolean expressions are False.

It is normal to end each Case with a Case Break command. The Case Break command will immediately halt execution of the Case statement and program execution will continue at the next statement in the program (following the Case statement). If you do not end each Case with a Case Break then execution will continue down through all the Cases until a Case Break is executed or the Case statement ends.

An example of a Case statement is:

Case Begin

Case (sAnimal = "Cat")

Move ("Meow") To sNoise

Case Break

Case (sAnimal = "Dog")

Move ("Arf") To sNoise

Case Break

Case (sAnimal = "Bird")

Move ("Tweet") To sNoise

Case Break

Case Else

Move ("") To sNoise

Case End


Looping Statements

Read more in these Looping Statements subtopics:

  • While

  • Repeat

  • For


While

Looping statements specify certain statements to be executed repeatedly.

A While statement contains a Boolean expression that controls the repeated execution of a list of statements. The list of statements is terminated by a Loop command.

The syntax of a While statement is:

While {Boolean-expression}

{statement 1}

{statement 2}

…

{statement n}

Loop

The Boolean expression controlling the repetition is evaluated before the statements are executed. The contained statements are executed as long as the expression is True. If the expression is False at the beginning then, the statements are not executed at all.

An example of a While statement is:

// initialize the variables

Move "" To sName

Move "" To sList

// Loop until Fred's name is entered

While ((sName) <> "Fred")

Move (GetNextName()) To sName // retrieve the next name

Move (sList + ";" + sName) To sList

Loop


Repeat

A Repeat statement contains a Boolean expression that controls the repeated execution of a list of statements. The list of statements begins with the Repeat command and is terminated by an Until command. The syntax of a Repeat statement is:

Repeat

{statement 1}

{statement 2}

…

{statement n}

Until {Boolean-expression}

The Boolean expression controlling the repetition is evaluated after the statements are executed. The contained statements are executed as long as the expression is False. If the expression is True at the beginning then the statements are executed only once.

An example of a Repeat statement is:

Move 1 To iPos

Repeat

Move (Mid(sMessage,1,iPos)) To sChar

Move (iPos + 1) To iPos

Until ((sChar = "Z") or (iPos > (Length(sMessage))))


For

The For statement is used to repeatedly execute a statement a fixed number of times. The number of repetitions is controlled by a variable that is incremented by one for each repetition until it matches the final-value. The syntax of a For statement is:

For {control-variable} From {initial-value} To {final-value}

(statement 1}

{statement 2}

…

{statement n}

Loop

Where:

  • {control-variable} is the integer variable that is used to control the number of repetitions;

  • {initial-value} is the initial setting for the control variable the first time the For statement is executed. The initial value can be a constant, variable, or expression of type Integer;

  • {final-value} is the highest value that the control-variable will be incremented to during execution of the For statement. When the control variable exceeds the final value then the For statement will stop looping. The final value can be a constant, variable, or expression of type Integer.

The statements contained by the For statement are executed once for every incremental value of the control variable in the range initial value to final value.

The Loop command marks the end of the block of statements that are contained by the For statement.

Here are some example For statements:

For iCount From 1 To 255

Move (sASCIIchars + Character(iCount)) To sASCIIchars

Loop

For iX From ixStart To ixStop

For iY From iyStart To iyStop

Move (GraphValue(iX, iY)) To iValue

Showln "Value at " iX "," iY " = " iValue

Loop

Loop


Global Procedures and Functions

An important part of the Visual DataFlex language is the concept of a routine. A routine is a named block of code comprising multiple statements that is designed to perform some operation. Each routine that you define can be invoked multiple times throughout your program to perform its designated operation.

A Visual DataFlex routine can assume two forms: a Procedure or a Function. The main difference between the two constructs is that a function has a return value, while a procedure does not.

Procedures and Functions can be passed data as parameters each time they are invoked. These parameters can be passed by value or by reference. Most parameters are passed by value.

Procedures and Functions can also declare local variables

Procedures and Functions can take two forms: they can be global or they can be object based methods.

The scope of a global procedure and function is an entire application. Global procedures and functions may be defined anywhere within an application and they can be called from anywhere within an application. A procedure or function is identified as being global by placing the keyword global in the procedures or function declaration as follows:

Procedure MyProcedure Global integer iValue string sValue

Function MyFunction Global integer iValue string sValue returns String

The global keyword is required. If omitted, the procedure or function will be defined as a method (object based).

Global procedures/functions are not object based. This means that they cannot be augmented, they are only created once and the name of the procedure/function cannot be used by any other global or method based procedure or function.

Global procedures/ functions should be used sparingly - use methods (object based procedures and functions) instead.

Refer to the section on Methods to learn more about object based procedures and functions, parameter passing and augmenting methods.

Read more in these Global Procedures and Functions subtopics:

  • Global Procedures

  • Global Functions

  • Forward Declarations

  • Design Considerations

  • Visual DataFlex built-in Function Library


Global Procedures

Declaring Global Procedures

A procedure declaration associates an identifier with a block of code as a procedure. That procedure can then be called within your program by a Send statement. The syntax for a procedure declaration is:

Procedure {procedure-name} Global [{type1} {param1} {type2} {param2} …]

[{variable-declarations}]

{statement 1}

{statement 2}

…

{statement n}

End_Procedure

Where:

  • {procedure-name} is the identifier associated with the procedure. You cannot use a Visual DataFlex reserved word as a procedure name.;

  • {type1} is the data type of parameter {param1};

  • {type2} is the data type of {param2} etc;

  • {variable-declarations} are the local variables that you can declare for the procedure and

  • {statement 1-n} are the statements that are executed whenever the procedure is called.

You can define zero or more parameters for each procedure. Each parameter behaves like a local variable that is initialized by the statement that calls the procedure. The data types of each parameter can be any of the Visual DataFlex fundamental types (Integer, Number, Real, etc.) as well as struct types and arrays.

By reference parameters can also be declared. In this case the parameter's name must be preceded by the keyword "ByRef". When a parameter is passed by reference, the procedure has direct read/write access to the passed variable. For more information refer to Parameter Passing.

Here are a few examples of procedure declarations:

Procedure DoShowCharacters Global String sMessage

// this procedure takes the string sMessage and outputs

// each character separately with intervening commas.

Integer iPos

Integer iLength

Move (Length(sMessage)) To iLength

For iPos From 1 To (iLength - 1)

Show (Mid(sMessage, 1, iPos)) ","

Loop

Showln (Mid(sMessage, 1, iLength))

End_Procedure

Procedure PrintValues Global Integer[] values

...

End_Procedure

//Assuming a struct type tCustomer has been declared somewhere else

Procedure PrintCustomer Global tCustomer cust

...

End_Procedure

Calling Global Procedures

Once a procedure has been declared you can execute it in your program with a Send statement. The syntax of the Send statement is:

Send {procedure-name} [{param1} {param2} … {paramN}]

Where:

  • {procedure-name} is the name of the previously declared procedure that you are executing;

  • {param1 .. N} are the parameters that you are passing to the called procedure. The number and order of parameters that you pass must match the list of parameters in the procedure's declaration. The parameters you pass can be either constants, variables or expressions, so long as they are of comparable type to the matching parameter in the procedure declaration.

Examples of calling a procedure are:

Send DoWriteCharacters "Some Character String"

Send DoWritePaddedString ("Message = " + sMessage) sPadCharacter

Parameter Passing and By Reference Parameters

The parameters of a procedure or function can be passed by reference, as well as by value.

When a parameter is passed by reference, the called procedure or function has direct read/write access to the passed variable. Changes made to the reference parameter inside the routine are reflected in the passed variable outside the method.

For more information refer to Parameter Passing


Global Functions

Declaring Global Functions

A function declaration associates an identifier with a block of code as a function. That function can then be called in you program within an expression. The syntax for a function declaration is:

Function {function-name} Global [{type1} {param1} {type2} {param2} …] ;

Returns {return-type}

[{variable-declarations}]

{statement 1}

{statement 2}

…

{statement n}

Function_Return {return-value}

End_Function

Where:

  • {function-name} is the identifier associated with the function. You cannot use a Visual DataFlex reserved word as a function name;

  • {type1} is the data type of parameter {param1};

  • {type2} is the data type of {param2} etc;

  • {return-type} is the declared type of the function. The value that is returned by the function will be compatible with this type.

  • {variable-declarations} are the local variables that you can declare for the function;

  • {statement 1-n} are the statements that are executed whenever the function is called and {return-value} is the value returned by the function.

You can define zero or more parameters for each function. Each parameter behaves like a local variable that is initialized by the statement that calls the function. The data types of each parameter can be any of the Visual DataFlex fundamental types (Integer, Number, Real, etc.) as well as struct types and arrays.

By reference parameters can also be declared. In this case the parameter's name must be preceded by the keyword "ByRef". When a parameter is passed by reference, the procedure has direct read/write access to the passed variable. For more information refer to Parameter Passing.

Each function must return a value. Execution of the function stops immediately after a Function_Return statement has been executed.

Here are a few examples of function declarations:

Function PowerOf Global Integer iX Integer iY Returns Integer

Integer iReturnVal

Integer iCount

Move 1 To iReturnVal

For iCount From 1 To iY

Move (iX * iReturnVal) To iReturnVal

Loop

Function_Return iReturnVal

End_Function

Function CreateValues Global Returns Integer[]

...

End_Function

//Assuming a struct type tCustomer has been declared somewhere else

Function CreateCustomer Global Returns tCustomer

...

End_Function

Calling Global Functions

Once a function has been declared you can execute it in your program within an expression. The syntax for calling a function in an expression is:

({function-name}([{param1}, {param2}, … {paramN}]))

Where:

  • {function-name} is the name of the previously declared function that you are executing;

  • {param1 .. N} are the parameters that you are passing to the called function. The number and order of parameters that you pass must match the list of parameters in the function's declaration. The parameters you pass can be either a constants, variables or expressions, so long as they are of comparable type to the matching parameter in the function declaration.

Examples of calling a function are:

If (CurrentUser() = "ADMIN") Showln "Administrator"

Move (PowerOf(2,8)) To iAnswer

Move (FormatString(sMyString)) To sMyString

Parameter Passing and By Reference Parameters

The parameters of a procedure or function can be passed by reference, as well as by value.

When a parameter is passed by reference, the called procedure or function has direct read/write access to the passed variable. Changes made to the reference parameter inside the routine are reflected in the passed variable outside the method.

For more information refer to Parameter Passing


Forward Declarations

Procedures and Functions can be forward declared using the Register_Procedure and Register_Function statements respectively. Forward declaring allows you to reference the procedure or function in some other piece of code before it is formally declared. The compiler would generate an error if you tried to do this without a forward declaration.

The syntax of the Register_Procedure and Register_Function statements are:

Register_Procedure {procedure-name} [{type1} {param1} {type2} {param2} …]

Register_Function {function-name} [{type1} {param1} {type2} {param2} …] Returns {return-type}

In other words, the syntax for forward declaring a procedure or function is the same as the first line of a regular procedure or function declaration except that the commands Register_Procedure and Register_Function are used instead. The type, number and order of parameters in the forward declaration must match the real procedure or function declaration.

Examples of forward procedure and function declarations are:

Register_Procedure DoWriteCharacters String sMessage

Register_Function Powerof Integer iX Integer iY Returns Integer

The above example would allow you to reference the registered procedure or function inside the body of some other procedure or function even though its formal declaration has not yet been made.


Design Considerations

As their declaration suggests Procedures and Functions are global (i.e., they can be called anywhere within the program).

In a carefully designed object-oriented program there is very little need to declare and use global procedures since each class should declare its own set of methods to perform all necessary operations.

Global functions are more useful since they could be viewed as an extension to Visual DataFlex's built-in function library.

However, it should be remembered that any new global procedure or function you write that is used by a class will break that classes encapsulation. One consequence of breaking encapsulation is to make it more complicated to re-use the class in other programs (a key benefit of object-oriented programming). The reason this occurs is because the class definition now contains an outside dependency upon any global functions that it uses. Its use is therefore dependent upon the existence of these functions.


Visual DataFlex built-in Function Library

Visual DataFlex contains a useful library of built-in global functions for performing a variety of programming operations. For detailed information on each built-in function, refer to the Visual DataFlex Language Reference Functions book.


Classes

A class is a structured type consisting of a fixed number of components. The possible components are methods , properties and private-objects. Class components are sometimes also referred to as members.

Read more about this in these Classes subtopics:

  • Class Declaration

  • Properties

  • Methods

  • Private Objects

  • The Self Keyword

  • Multiple Inheritance


Class Declaration

A class is declared using a Class Declaration statement. The syntax of the class declaration statement is:

Class {class-name} is a {superclass}

[

Procedure Construct_Object

Forward Send Construct_Object

{property-declarations}

{private-objects}

End_Procedure

]

[{method-declarations}]

End_Class

Where:

  • {class-name} is the name of the new class and

  • {superclass} is the name of an existing class that the new class is derived from. Each class inherits all components of its superclass. Each inherited method may be augmented, overridden or canceled inside the new class declaration. You may define new properties, methods and private objects inside the class constructor method (this is fully explained in the following sections). The end of the class declaration block is marked by the End_Class command.

Visual DataFlex provides a library of high-level classes that are designed to perform most of the functionality you need for building database applications. You can use these built-in classes as the foundation of your own extended class library from which you would construct your applications. The Class Reference manual contains a complete listing of every class in the Class Library.

An example of a simple class declaration is:

Class cOK_Button is a Button

End_Class

This example declares a new class called cOK_Button that is based on the built-in Button class. This new class will perform exactly as its superclass (Button). Since there are no new properties, methods or private objects there would be no new behavior associated with the new class.

Visual DataFlex naming convention suggests that you prefix your new class names with a "c". This will help you ensure that no naming conflict will occur between your class and other identifiers in your program.


Properties

A property definition declares a named data element of the class. Together the class properties define the attributes of each instance of a class. Properties are not variables.

Examples of properties are the caption of a panel, the location of a checkbox, the main file of a data dictionary, etc..

Read more about this in these Properties subtopics:

  • Declaring Properties

  • Setting Property Values

  • Reading Property Values

  • Forward Declaring Properties


Declaring Properties

The syntax for declaring a new property is:

Property {property-type} {property-name} {initial-value}

Where:

  • {property-type} is the data type for the new property. This can be any of the Visual DataFlex fundamental types (Integer, Number, Real, etc.) as well as a struct type or array.

  • {property-name} is the identifier that you assign to the property. You can not use a Visual DataFlex reserved word as a property identifier;

  • {initial-value} is the default setting for the property for each instance of the class.

Class properties must be declared inside the Constructor method of the class. The name of every class's constructor method is Construct_Object. Refer to the sections on Constructors and Destructors for more information. An example of declaring a class with two new properties is:

Class cOK_Button is a Button

// Constructor:

Procedure Construct_Object

Forward Send Construct_Object // very important!

Property Integer piClickCount 0

Property String psBusyLabel "-Busy-"

Property String[] psValues

End_Procedure

End_Class

This example declares a class, cOK_Button, with three new properties, piClickCount, psBusyLabel and psValues. Note that the properties are declared inside the class constructor method, Construct_Object. An important part of the Construct_Object method is the "Forward Send Construct_Object" statement. This is the line that ensures that all of the definitions in the superclass's constructor are inherited by the new class.

Visual DataFlex property naming convention suggests that you prefix all property names with p followed by a letter that indicates its data type(i, n, r, b, d, s, v, h). Refer to the appendix on Naming Conventions for more information.


Setting Property Values

Object properties are set by executing a Visual DataFlex Set statement. The syntax of the set statement is:

Set {property-name} [of {object-ID}] To {value}

Where:

  • {property-name} is the name of the property that is being set;

  • {object-ID} is a handle to the object whose property is being set and

  • {value} is the new value of the property. The value can be any constant, variable or expression that is of a compatible type to the property.

If you are setting a property of the current object (current instance of the class) then you can use the Self Keyword as the object-id. Refer to the section on The Self keyword for more details. Alternatively you can omit the object-id clause completely and Visual DataFlex will automatically reference the current object. An example of a class declaration that sets properties of the current object are:

Class cOK_Button is a Button

// Constructor:

Procedure Construct_Object

Forward Send Construct_Object // very important!

Property Integer piClickCount 0

Property String psBusyLabel ""

Set Label To "OK"

// same as writing… Set Label of Self To "OK"

Set Help_ID To C_OkButtonHelp

Set Status_Help To "Click OK to begin operation"

Set psBusyLabel To "-Busy-"

End_Procedure

End_Class

The above example sets four properties: Label, Help_ID and Status_Help are properties that were inherited from the superclass; psBusyLabel is a new property. A property must be declared before you can set it.

In the above example all the property settings occur in the constructor method. However you can set properties of the current object inside any method that is part of the class definition.

If you are setting a property of some other object then you need to provide a handle to that object in the {object-ID} part of the Set statement. The Visual DataFlex Handle type can be used to create variables or properties for storing object handles. Examples of set statements that set properties of some external object are:

Set Checked_State of oMarried_Checkbox To True

Set Minimum_Position of oSpinForm To 0

Set Maximum_Position of oSpinForm To 100

In the above examples, oMarried_Checkbox is the name of an instance of the Checkbox class and oSpinform is an instance of the SpinForm class. Both these classes are built into the Visual DataFlex class library.

Notes

You cannot use the Move statement to set a property value. The Move statement is only for assigning values to variables.


Reading Property Values

Object properties are read by executing a Visual DataFlex Get statement. The syntax of the get statement is:

Get {property-name} [of {object-ID}] To {variable-id}

Where:

  • {property-name} is the name of the property that is being read;

  • {object-ID} is a handle to the object who's property is being read and,

  • {variable-id} is the name of a variable that will receive the property's value. The receiving variable can be any variable that is of a compatible type to the property.

If you are reading a property of the current object (current instance of the class) then you can use the Self keyword as the object-id. Refer to the section on The Self Keyword for more details. Alternatively you can omit the object-id clause completely and Visual DataFlex will automatically reference the current object. An example of a class declaration that reads properties of the current object are:

Class cCount_Button is a Button

// Constructor:

Procedure Construct_Object

Forward Send Construct_Object // very important!

Property Integer piClickCount 0

End_Procedure

Procedure OnClick

Integer iCount

Get piClickCount To iCount

Move (iCount + 1) To iCount

Set piClickCount To iCount

Set Label To iCount

End_Procedure

End_Class

The above example declares a class, based on the Button class, with a new property, piClickCount. The button class is designed to execute a method called OnClick each time the button is 'clicked' on by the mouse (this is called an event method). The OnClick method has been defined to read the piClickCount property into a variable, increment it, set the property back to its incremented value and also display the value in the button's label (by setting the label property).

If you are reading a property of some other object then you need to provide a handle to that object in the {object-ID} part of the Get statement. The Visual DataFlex Handle type can be used to create variables or properties for storing object handles. Examples of get statements that read properties of some external object are:

Get Checked_State of oMarried_Checkbox To bChecked

Get Minimum_Position of oSpinForm To iMax

Get Maximum_Position of oSpinForm To iMin

In the above examples, oMarried_Checkbox is an instance of the Checkbox class and oSpinform is an instance of the SpinForm class. Both these classes are built into the Visual DataFlex class library.


Forward Declaring Properties

Properties can be forward declared using the Register_Procedure and Register_Function statements. Forward declaring allows you to reference a property in some method before it is formally declared. The compiler would generate an error if you tried to do this without a forward declaration.

The syntax for forward declaring a property is:

Register_Procedure {property-name} Set {type} {param}

Register_Function {property-name} Returns {type}

Where:

  • {property-name} is the name of the property that you are forward declaring;

  • {type} is the data type of the property; and

  • {param} and be any variable identifier ({param} is not used).

The Register_Procedure part of the forward declaration is needed when you are forward referencing a property in a Set statement. The Register_Function part of the forward declaration is needed when you are forward referencing a property in a Set statement. If you only have one type of forward reference then you can omit the unused part of the forward declaration.

Examples of forward declaring properties follows:

Register_Procedure Set Height Integer iX

Register_Function Height Returns Integer

Register_Procedure Set Width Integer iY

Register_Function Width Returns Integer

The above example would allow you to reference the registered properties inside the body of some method declaration even though the properties' own formal declaration have not yet been made.


Methods

A method is a procedure or function that is declared inside the Class definition statement and performs an operation, typically on an instance of the class.

Visual DataFlex supports three different kinds of methods.

  • Procedures

  • Procedure Set

  • Function

Procedures perform discrete operations on the object or on behalf of the object. Procedure Set methods perform discrete operations then set some state, or attribute, of the object. Functions perform a discrete operation then return some state, or attribute, of the object.

Notes

Class methods are not like the Global Procedures and Functions that are described in Global Procedures and Functions. Global procedures and functions are declared and called differently and are intended for a different purpose than class methods.

Read more about this in these Methods subtopics:

  • Declaring Procedure Methods

  • Declaring Procedure Set Methods

  • Declaring Function Methods

  • Forward Declaring Methods

  • Event Handlers

  • Calling Procedure Methods

  • Calling Function Methods

  • Calling Procedure Set Methods

  • Parameter Passing

  • Passing Variable Number of Parameters

  • Constructors and Destructors

  • Augmenting Inherited Methods

  • Method Overloading


Declaring Procedure Methods

A Procedure method performs a discrete operation on the object or on behalf of the object. The syntax for declaring a Procedure Method is:

Procedure {method-name} [{type1} {param1} {type2} {param2} …]

[{variable-declarations}]

{statement 1}

{statement 2}

…

{statement n}

End_Procedure

Where:

  • {method-name} is the name of the procedure method. You cannot use a Visual DataFlex reserved word as a method name.

  • {type1} is the data type of parameter {param1}, {type2} is the data type of {param2} etc;

  • {variable-declarations} are the local variables that you can declare for the method and

  • {statement 1-n} are the statements that are executed whenever the method is called.

Parameters

You can define zero or more parameters for each procedure method. Each parameter behaves like a local variable that is initialized by the statement that executes the method. The data types of each parameter can be any of the Visual DataFlex fundamental types (Integer, Number, Real, etc.) as well as struct types and arrays.

By reference parameters can also be declared. In this case the parameter's name must be preceded by the keyword "ByRef". When a parameter is passed by reference, the procedure has direct read/write access to the passed variable. For more information refer to Parameter Passing.

Examples

Here is an example of a class definition with a few procedure method declarations:

Class cCount_Button is a Button

// Constructor:

Procedure Construct_Object

Forward Send Construct_Object // very important!

Property Integer piClickCount 0

End_Procedure

Procedure DoPrint String sMessage

Showln "Our message today is:"

Showln sMessage

Showln

End_Procedure

Procedure OnClick

Integer iCount

Get piClickCount To iCount

Move (iCount + 1) To iCount

Set piClickCount To iCount

Send DoPrint ("Click count = " + String(iCount))

End_Procedure

Procedure PrintValues Integer[] values

...

End_Procedure

//Assuming a struct type tCustomer has been declared somewhere else

Procedure PrintCustomer tCustomer cust

...

End_Procedure

End_Class

The methods in this exampledemonstrates various procedure method declarations. The Button class is designed to execute a method called OnClick each time the button is clicked on by the mouse. The OnClick method we have defined will increment a property then execute the DoPrint method to print the value of that property.

A Procedure method executes until it reaches an End_Procedure or it encounters a Procedure_Return command. Procedure_Return can be used to halt execution of a Procedure method at any point in the procedure body. For the OnClick method in the previous example could be re-written as follows:

Procedure OnClick

Integer iCount

Get piClickCount To iCount

If (iCount >= 1000) Begin

Error 300 "You have tried to too many times. Give up!"

Procedure_Return

End

Move (iCount + 1) To iCount

Set piClickCount To iCount

Send DoPrint ("Click count = " + String(iCount))

End_Procedure

Visual DataFlex also supports method overloading. Refer to the section on Method Overloading for information on declaring overloaded Procedure Methods.


Declaring Procedure Set Methods

A Procedure Set method performs a discrete operation, then sets some state, or attribute, of the object. Procedure Set mimics the setting of a property. A Procedure Set method should not take more than one parameter, it was designed for use with properties. The syntax for declaring a Procedure Set Method is:

Procedure Set {method-name} [{type} {param}]

[{variable-declarations}]

{statement 1}

{statement 2}

…

{statement n}

End_Procedure

Where:

  • {method-name} is the name of the Procedure Set method. You cannot use a Visual DataFlex reserved word as a method name.

  • {type} is the data type of parameter {param}.

  • {variable-declarations} are the local variables that you can declare for the method and

  • {statement 1-n} are the statements that are executed whenever the method is called.

Parameters

You can define zero or more parameters for each Procedure Set method. Each parameter behaves like a local variable that is initialized by the statement that executes the method. The data types of each parameter can be any of the Visual DataFlex fundamental types (Integer, Number, Real, etc.) as well as struct types and arrays.

By Reference parameters can also be declared. In this case the parameter's name must be preceded by the keyword "ByRef". When a parameter is passed by reference, the procedure has direct read/write access to the passed variable. For more information to Parameter Passing.

Examples

Here is an example of a class definition that declares a Procedure Set method:

Class cCount_Button is a Button

// Constructor:

Procedure Construct_Object

Forward Send Construct_Object // very important!

Property Integer Private_piClickCount 0

End_Procedure

Procedure Set piClickCount Integer iValue

Set Private_piClickCount To iValue

Set Label To iValue

End_Procedure

Function piClickCount Returns Integer

Integer iValue

Get Private_piClickCount To iValue

Function_Return iValue

End_Function

Procedure OnClick

Integer iCount

Get piClickCount To iCount

Move (iCount + 1) To iCount

Set piClickCount To iCount

End_Procedure

End_Class

The class declaration in the above example is a new Button class that is designed to record how many times it has been clicked and display that number in the button's label. A Procedure Set method has been defined to take care of setting an internal property that records the number of clicks. This Procedure Set will also perform the task of setting the button's label. A Function has also been designed to retrieve the value of the internal property. Therefore, we have defined a new interface to set and read the number of times that the button has been clicked. The new interface guarantees that each time the piClickCount property is set, the button's label will also be adjusted.

The Button class is designed to execute a method called OnClick each time the button is clicked by the mouse. The OnClick method we have defined reads the piClickCount property to a local variable, increments it, then sets piClickCount to the incremented value. The Get piClickCount statement will execute the piClickCount function method and the Set piClickCount statement executes the piClickCount procedure set method. Thus the declaration of a Procedure Set and a Function method simulate a new class property called piClickCount.

Visual DataFlex also supports method overloading. Refer to the section on Method Overloading for information on declaring overloaded Procedure Methods.


Declaring Function Methods

A Function method performs a discrete operation then returns some state, or attribute, of the object. Calling a Function method simulates reading a property. The syntax for declaring a Function Method is:

Function {method-name} [{type1} {param1} {type2} {param2} …] Returns {return-type}

[{variable-declarations}]

{statement 1}

{statement 2}

…

{statement n}

[Function_Return {return-value}]

End_Procedure

Where:

  • {method-name} is the name of the function method. You cannot use a Visual DataFlex reserved word as a method name.

  • {type1} is the data type of parameter {param1}, {type2} is the data type of {param2} etc;

  • {return-type} is the declared type of the function method. The value that is returned by the method will be compatible with this type.

  • {variable-declarations} are the local variables that you can declare for the method.

  • {statement 1-n} are the statements that are executed whenever the method is called.

  • {return-value} is the value returned by the method.

Parameters

You can define zero or more parameters for each function method. Each parameter behaves like a local variable that is initialized by the statement that executes the method. The data types of each parameter can be any of the Visual DataFlex fundamental types (Integer, Number, Real, etc.) as well as struct types and arrays.

By Reference parameters can also be declared. In this case the parameter's name must be preceded by the keyword "ByRef". When a parameter is passed by reference, the function has direct read/write access to the passed variable. For more information refer to Parameter Passing.

Return Value

Each function method must return a value. Execution of the method stops immediately after a Function_Return statement has been executed.

Refer to Declaring Procedure Set Methods for an example on declaring a function method.

Visual DataFlex also supports method overloading. Refer to the section on Method Overloading for information on declaring overloaded Procedure Methods.


Forward Declaring Methods

Methods can be forward declared using the Register_Procedure and Register_Function statements respectively. Forward declaring allows you to reference one procedure or function in some other method before it is formally declared. The compiler would generate an error if you tried to do this without a forward declaration.

Forward declaring Procedure methods

The syntax for forward declaring a Procedure method is:

Register_Procedure {procedure-name} [{type1} {param1} {type2} {param2} …]

Forward declaring Procedure Set methods

The syntax for forward declaring a Procedure Set method is:

Register_Procedure Set {procedure-name} [{type1} {param1} {type2} {param2} …]

Forward declaring Function methods

The syntax for forward declaring a Function method is:

Register_Function {function-name} [{type1} {param1} {type2} {param2} …] ;

Returns {return-type}

In other words, the syntax for forward declaring a method is the same as the first line of a regular method declaration except that the commands Register_Procedure and Register_Function are used instead. The type, number and order of parameters in the forward declaration must match the real procedure or function declaration.

Examples of forward declaring methods follows:

Register_Procedure DoWriteCharacters String sMessage

Register_Procedure Set Height Integer iX

Register_Procedure Set Width Integer iY

Register_Function Height Returns Integer

Register_Function Width Returns Integer

The above example would allow you to reference the registered methods inside the body of some other method declaration even if its own formal declaration has not yet been made.


Event Handlers

Event handlers are a specialized methods. Each class in the Visual DataFlex class library will raise certain events during important operations. For example, the TreeView class supports many Event events such as:

  • OnCreateTree, this event is executed when a TreeView object is created.

  • OnItemChanged, this event is executed whenever the focused item in a treeview moves from one item to another, and so on.

These events are the points at which you can design specialized behavior for the class during important operations. You design the specialized behavior by writing the appropriate event handler in your class declaration. You need to study the documentation for each class to determine which event methods are available for augmentation.

An example of augmenting Event methods follows:

Class cMyButton is a Button

Procedure OnClick

Send Info_Box "I have been clicked"

End_Procedure

End_Class

In this example we have designed a new button class called cMyButton. The OnClick event has been augmented so that each time the button is clicked a message will be displayed.


Calling Procedure Methods

A procedure method is executed by calling the method. This is sometimes referred to as sending a message. The syntax for calling a procedure method is:

Send {method-name} [of {object-ID}] {param1 … paramN}

Where:

  • {method-name} is the name of the procedure method being executed;

  • {object-ID} is a handle to the object that will receive the instruction to execute the method;

  • {param1..paramN} are the parameters that you are passing to the called method.

The order of the parameters must match the list of parameters in the method's declaration. The parameters you pass can be either constants, variables or expressions, so long as they are of comparable type to the matching parameters in the method declaration.

If you are calling a method of the current object (current instance of the class) then you can use the self keyword as the object-id. Refer to The Self Keyword section for more details. Alternatively, you can omit the object-id clause completely and Visual DataFlex will automatically reference the current object.

Examples of calling procedure methods follow:

Class cMyEdit is a Edit

Procedure DoAddName

String sName

Get psName To sName

Send Append_Text sName

// same as writing Send Append_Text of Self sName

End_Procedure

Procedure Construct_Object

Forward Send Construct_Object

Property String psName Public "John"

On_Key Key_Alt+Key_D Send DoAddName

End_Procedure

End_Class

The above example declares a class cMyEdit based on the Edit class. The Contruct_Object procedure defines an accelerator-key (Alt+D) that will call the DoAddName method. The DoAddName method will read a string property and append it to the end of the text by calling the inherited Append_Text method.

If you are calling a method of some other object then you need to provide a handle to that object in the {object-ID} part of the Send statement. The Visual DataFlex Handle type can be used to create variables or properties for storing object handles. Examples of Send statements that call methods of some external object are:

Send Beginning_of_line of oEdit

Send Goto_Line of oEdit iLineNumber

Send Move_Absolute of oEdit 20 5

In the above example, oEdit is an instance of the Edit class.

Refer to the section on Method Overloading for more information on calling procedure methods.


Calling Function Methods

Calling a Function method is complementary to calling a Procedure Set method (i.e., if a Procedure Set method is used to set a property), then there is usually a Function method that is used to read it. A function method is executed by using a Get statement. The general syntax of the Get statement is:

Get {method-name} [of {object-ID}] {param1 … paramN} To {receiving-variable}

where:

  • {method-name} is the name of the Function method that is being executed:

  • {object-ID} is a handle to the object who's method is being executed

  • {param1 … paramN} are all parameters that are passed to the Function method and

  • {receiving-variable} is the variable that is assigned the value returned by the Function method

The order of the parameters must match the list of parameters in the method's declaration. The parameters you pass can be either constants, variables or expressions, so long as they are of comparable type to the matching parameters in the method declaration.

If you are calling a Function method of the current object (current instance of the class) then you can use the Self keyword as the object-id. Refer to The Self Keyword section for more details. Alternatively you can omit the object-id clause completely and Visual DataFlex will automatically reference the current object.

Function methods are often used, in conjunction with Procedure Set methods, to simulate a Property. If the function method does not contain any parameters then the syntax for calling the method is identical to reading a property.

Some examples of calling Function Methods are:

Get Can_Delete To bCanDelete

For iCount From 0 To 9

Get String_Value of oAnArray iCount To sValue

Showln sValue

Loop

// Call some function methods of the current object

Get Field_Current_Value Field Customer.Name To sName

Get Field_Mask Field Customer.Phone To sPhoneMask

// Call some function methods of an 'external' data dictionary

Get Field_Default_Value of oOrderDD Field Order.Date To dOrderDate

Get Field_Mask of oOrderDD Field Order.ContactPhone To sMask

Refer to the DataDictionary class in the Class Reference for more information about Field and File_Field properties.

Refer to the section on Method Overloading for more information on calling procedure methods.


Calling Procedure Set Methods

A Procedure Set method is executed by using a Set statement. The general syntax of the Set statement is:

Set {method-name} [of {object-ID}] {param1 … paramN} To {value1 … valueN}

Where:

  • {method-name} is the name of the method that is being executed;

  • {object-ID} is a handle to the object who's method is being executed;

  • {param1 … paramN} and {value1 … valueN} are all parameters that are passed to the Procedure Set method.

The order of the parameters must match the list of parameters in the method's declaration, this is explained in further detail below. The parameters you pass can be either constants, variables or expressions, so long as they are of comparable type to the matching parameters in the method declaration.

If you are calling a Procedure Set method of the current object (current instance of the class) then you can use the Self keyword as the object-id. Refer to The Self Keyword section for more details. Alternatively, you can omit the object-id clause completely and Visual DataFlex will automatically reference the current object.

Whether you place your parameters as {param1 … paramN} or {value1 … valueN} is really a matter on convention and will differ depending on the specific Procedure Set method you are executing. However, you must pass at least one parameter as value1.

Simple Procedure Set

Procedure Set methods are used primarily to simulate setting a Property. These are called simple procedure set methods. The syntax for calling simple procedure set methods is:

Set {method-name} [of {object-ID}] To {value}

This is exactly the same as the syntax for setting a property value. Here are some examples of calling a simple Procedure Set method.

// Call some Procedure Set methods of the "current" object

Set Label To "Customer Name:" // set the label of the current object

Set Color To clBtnFace // set the color of the current object

// Call some Procedure Set methods of an "external" object

Set Label of oAddrForm To "Customer Address:"

Set Color of oAddrForm To clGreen

In the above example, oAddrForm is an instance of the Form class. As the above examples show, executing a simple Procedure Set method is indistinguishable from setting a property.

Complex Procedure Set

Some Procedure Set methods are used to set two or more values at once. These are called complex procedure set methods. The general syntax for executing a complex procedure set is:

Set {method-name} [of {object-ID}] To {value1 … valueN}

Here are some examples of calling complex Procedure Set methods.

Set Location To 10 10 // set location of the current object to 10,10

Set Size To iHeight iWidth

// now execute procedure sets of an "external" object

Set Location of oAddrForm To 10 10

Set Size of oAddrForm To iHeight iWidth

In the above example, oAddrForm is an instance of the Form class.

Notes

Complex Procedure Set methods are being phased out of Visual DataFlex in preference for multiple simple procedure set methods or properties.

Procedure Set With Special Parameters

Some Procedure Set methods require special parameters to be passed. The parameters are usually designed to identify which property the method will set from a list, or array, of similar properties. Common syntax for executing a procedure set with parameters is:

Set {method-name} [of {object-ID}] {item-id} To {value}

Where:

  • {item-id} denotes the index to an array of properties. {Item-id} should be an integer constant, variable or expression.

The DataDictionary class contains a set of properties that require calling procedure set methods with special parameters. The general syntax for calling these procedure set methods is:

Set {method-name} [of {object-ID}] Field {file.field-id} To {value}

Set {method-name} [of {object-ID}] File_Field {file.field-id} To {value}

Where:

  • {file.field-id} is a data file field identifier (refer to Data File Type for more information). Procedure Set methods that take {file.field-id} parameters exist primarily in the Visual DataFlex DataDictionary class.

Here are some examples of calling Procedure Set methods with special parameters:

For iCount From 0 To 9

Set Array_Value iCount To 0

Loop

In this example, the Array_Value method is being called to set the indexed property to 0 for the first 10 items. Some more examples are:

// set some properties of the current object

Set Field_Current_Value Field Customer.Name To "Fred"

Set Field_Mask Field Customer.Phone To "(###)########"

// set some properties of an 'external' data dictionary

Set Field_Default_Value of oOrderDD Field Order.Date To dToday

Set Field_Mask of oOrderDD Field Order.ContactPhone To "(###)########"

Refer to the DataDictionary class in the Class Reference for more information about Field and File_Field properties.

Some Procedure Set methods that require special parameters are also written the following way:

Set {method-name} [of {object-ID}] Item {item-id} To {value}

The Item keyword is can be used to designate that the first parameter {item-id} is an item or indexed value. The inclusion or exclusion of the Item keyword has no effect on your program. The use of this keyword is being phased out of Visual DataFlex.

Refer to the section on Method Overloading for more information on calling procedure methods.


Parameter Passing

The parameters of called procedures or functions can be passed by value or by reference. This section describes the difference between these two techniques.

By Value Parameters

When a parameter is passed by value it acts like a local variable in the called procedure or function. Changes made to the value parameter inside method the do not affect the value of the passed variable outside the method. i.e. the called method is only passed the value of the passed variable or expression, not the variable itself. This is the default behavior of procedure or method parameters in Visual DataFlex.

By Reference Parameters

When a parameter is passed by reference, the called procedure or function has direct read/write access to the passed variable. Changes made to the reference parameter inside the method are reflected in the passed variable outside the method.

This technique is often applied when there’s a need for multiple return values, or for in-out parameters which pass one value to the called method and are allowed to be modified and contain a different value when the execution returns to the caller. The latter is commonly used in COM event methods, as a means of allowing the client code to cancel or modify a certain action or default behavior.

Declaring By Reference Parameters

To specify that a parameter is to be passed by reference you must precede the parameter with the ByRef symbol in the procedure/function declaration. For example...

Procedure AddOne Integer ByRef iValue

Move (iValue + 1) to iValue

End_Procedure

Integer iCount

Move 0 to iCount

Send AddOne (&iCount)

The above procedure declares one by reference parameter (iValue). When the procedure is called the integer variable that it passed to the procedure will be incremented by 1.

'&' (by reference) Operator

To pass an argument by reference rather than by value, you must prefix the passed parameter with the unary & operator. The prefix and parameter must be enclosed in an expression (that is, the & operator and variable name must be enclosed within parentheses). The passed parameter must also be a variable. e.g. (&MyVariable) will pass on MyVariable by reference rather than by value. This enables the called method to modify the contents of MyVariable directly, so that when the call returns, MyVariable may contain a different value.

The following is an example of passing an argument by reference:

Procedure GetData String ByRef sData

Move "Here is some data" to sData

End_Procedure

String sMyString

Send GetData (&sMyString)

// The value of sMyString is now "Here is some data"

Array and Struct Parameters

Array and struct parameters automatically utilizes the copy-on-write concept. So passing an array/struct by value does not cause the entire array/struct to be copied into a new memory location, unless the procedure or function modifies any value in the array/struct. For example, the WriteNames procedure in the examples below is memory efficient because it is only reading values from the array.

Struct tNameStruct

string FirstName

string LastName

End_Struct

tNameStruct[] gStudents

Procedure ReadNames Global tNameStruct[] byRef Names

String sFirstName sLastName

Integer iCount

Move 0 to iCount

Direct_Input "Names.txt"

While (Not(seqeof))

Readln sFirstName sLastName

If (Not(seqeof)) Begin

Move sFirstName to Names[iCount].FirstName

Move sLastName to Names[iCount].LastName

Increment iCount

End

Loop

Close_Input

End_Procedure

Procedure WriteNames Global tNameStruct[] Names

Integer iName icName

Move (SizeOfArray(Names)) to icName

For iName from 0 to (icName – 1)

Showln Names[iName].FirstName " " Names[iName].LastName

Loop

End_Procedure

Send ReadNames (&gStudents)

Send WriteNames gStudents

The procedure ReadNames is passed a reference to a tNameStruct array (i.e. a by reference parameter). The procedure fills the array with values read from a text file. ReadNames uses by reference parameter passing because it is modifying the contents of the array.

The procedure WriteNames lists each element in the tNameStruct array passed to it. WriteNames does not need to pass the array by reference because it is only reading values from the array, so the contents of the array is not copied even though it’s passed by value due to the built-in copy-on-write optimization for struct/array parameter types.


Passing Variable Number of Parameters

When calling a procedure or function method you are allowed to pass a variable number of parameters. If a procedure or function might receive a variable number of parameters you need to check how many parameters were actually passed and only use the parameters actually passed.

Num_Arguments

You can check for the number of parameters passed by using the Num_Arguments variable.

In the following example the procedure ApplyDiscount checks to see if one or two parameters was passed and it adjusts its logic accordingly:

Class cMyButton is a Button

Procedure ApplyDiscount number nAmount integer iPercent

Number nDiscount

If (Num_Arguments=2) begin

Move (nAmount/100 * iPercent) to nDiscount

End

Else Begin

// if percent not passed apply standard discount of 10%

Move (nAmount * .10) to nDiscount

end

Send ShowDiscount nDiscount

End_Procedure

Procedure OnClick

Send ApplyDiscount nAmount 50 // apply 50% discount

Send ApplyDiscount nAmount // apply standard discount

End_Procedure

End_Class

In the above example, ApplyDiscount could be successfully called passing it 1 or 2 parameters.

If a parameter is not passed, the variable used for that parameter cannot be used in procedure or function – although it is named, it does not exist. For example, the following would generate a runtime error when passed a single parameter:

Procedure ApplyDiscount number nAmount integer iPercent

Number nDiscount

If (Num_Arguments=1) begin

Move 10 to iPercent // this will not work!

End

Move 10 (nAmount/100 * iPercent) to nDiscount

Send ShowDiscount nDiscount

End_Procedure

In the above example, you may not move a value to iPercent because the variable is not defined if the parameter is not passed. The proper code for this sample would be:

Procedure ApplyDiscount number nAmount integer iPercent

Number nDiscount

Integer iApplyPercent

If (Num_Arguments=1) begin

Move 10 to iApplyPercent

End

Else begin

Move iPercent to iApplyPercent

End

Move 10 (nAmount/100 * iApplyPercent) to nDiscount

Send ShowDiscount nDiscount

End_Procedure

Note that this technique is also supported for global procedures and functions.


Constructors and Destructors

Constructors and Destructors are specialized event methods that control construction and destruction of objects. Each is specified as a method of the class just like any other method except that the method names Construct_Object and Destroy_Object are used.

Construct_Object

Construct_Object is the name used to declare a constructor method for your class. Constructors are used to create new properties, create nested objects and initialize the object. The syntax for declaring a constructor method is:

Procedure Construct_Object

{variable declarations}

{statements}

Forward Send Construct_Object

{more statements}

End_Procedure

The first action of a constructor is almost always to call the inherited constructor to create and initialize all the inherited attributes of the object. This is performed by using the Forward Send Construct_Object statement. Refer to Augmenting inherited Methods.

An example of a class declaration with a constructor method is:

Class cMyEdit is a Edit

Procedure Construct_Object

Forward Send Construct_Object

Property String psName "John"

On_Key Key_Alt+Key_X Send Delete_Data

End_Procedure

End_Class

In this example, the constructor firsts initializes the inherited attributes, then creates a new property, then executes an On_Key statement to declare an accelerator key setting.

Refer to Declaring Properties and Private Objects for more information.

Destroy_Object

Destroy_Object is the name used to declare a destructor method for your class. Destructors are invoked whenever an object is being destroyed. The syntax for declaring a destructor method is:

Procedure Destroy_Object

{variable declarations}

{statements}

Forward Send Destroy_Object

{more statements}

End_Procedure

The first action of a destructor is almost always to call the inherited destructor. This is performed by using the Forward Send Destroy_Object statement. Refer to Augmenting Inherited Methods.

An example of a class declaration with a destructor method is:

Class cMyEdit is a Edit

Procedure Destroy_Object

Send Info_Box "We are destroying this object"

Forward Send Destroy_Object

End_Procedure

End_Class

Notes

You should never send the messages Construct_Object or Destroy_Object. These are event methods that are automatically executed whenever an object is being created or destroyed. Objects are constructed when an object declaration statement is executed. If you wish to manually destroy an object you should execute the Destroy message.

For more information refer to: the Destroy method in the class reference and Dynamic Objects.


Augmenting Inherited Methods

Each method that is inherited from a superclass can be augmented to perform some different action in the new class. If there is no augmentation then the inherited methods will perform as defined in the superclass.

An inherited method is augmented simply by creating a new definition for the method in your class definition. The following example demonstrates augmenting an inherited method:

Class cMessageButton1 is a Button

Procedure DoShowSomething

Send Info_Box "First Message"

End_Procedure

Procedure OnClick

Send DoShowSomething

End_Procedure

End_Class

Class cMessageButton2 is a cMessageButton1

Procedure DoShowSomething

Send Info_Box "Second Message"

End_Procedure

End_Class

In this example, the first class cMessageButton1 defines a new method DoShowSomething that displays the message "First Message". Thus object of this class, when clicked, would show the message "First Message". The second class cMessageButton2 inherits DoShowSomething and OnClick from cMessageButton1, but it augments DoShowSomething to display a different message. An object of this class, when clicked would show the message "Second Message."

Message Forwarding

A more sophisticated way to augment an inherited method involves forwarding. Message forwarding allows you to augment an inherited method in such a way that it can perform its inherited action and some new action.

All three method types can be forwarded (Procedure, Procedure Set and Function). Messages are forwarded by executing a Forward statement. The syntax for forwarding a message is different for each type of method as outlined below:

Forwarding Procedure Methods

Forward Send {method-name} {Param1 … ParamN}

Forwarding Procedure Set Methods

Forward Set {method-name} {Param1 … ParamN} To {value1 … valueN}

Forwarding Function Methods

Forward Get {method-name} {Param1 … ParamN} To {receiving-variable}

You can see that the syntax for forwarding a message is the same as the syntax for executing a method except that the Forward command is appended to the front of each statement. There is also no object reference when forwarding a method. You are always forwarding the message to the superclass of the current object.

An example of method augmentation with message forwarding follows:

Class cMessageButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property String psMessage Public "First Message"

End_Procedure

Procedure OnClick

String sMessage

Get psMessage To sMessage

Send Info_Box sMessage

End_Procedure

End_Class

Class cShowLabelButton is a cMessageButton

Procedure Set Label String sLabel

Forward Set Label To sLabel

Set psMessage To sLabel

End_Procedure

End_Class

In this example, the first class augments the Construct_Object method cMessageButton to define a new string property. Note that Construct_Object is forwarded to ensure that all the superclass's attributes are inherited. The OnClick event method is augmented to show the value of the new string property in a message box. The second class cShowLabelButton inherits the behavior of cMessageButton, but it also augments the inherited Procedure Set Label method so that the message that is shown when the button is clicked is the same as the button's label. The Forward Set Label statement is important because, without it, the Set Label method would no longer be able to set the button's label.


Method Overloading

Many object oriented languages support alternate methods with different parameter lists. This is referred to as overloading. There are at least two types of overloading:

Overloading on Parameter Count

When overloading on parameter count methods with different numbers of parameters are treated as if they are different methods (in fact, they are). When you call a method the system will check the number of parameters passed and search for a method of this name with the same number of parameters. If found, the message is sent. If not found, an error occurs. This type of overloading is most appropriate for loosely typed languages like Visual DataFlex.

Overloading on Parameter Count and data type

This method takes overloading one step further and looks at both parameter count and type when resolving a message. When you call method the system will check the number of parameters passed and the datatype of each parameter, and search for a method of that name with the same number of parameters and data-types. If found, the message is sent. If not found, an error occurs. This type of overloading is most appropriate for strongly typed languages such s Java.

Visual DataFlex supports the ability to declare a method as being overloaded. It overloads on parameter count only. Methods are not overloaded by default.

Declaring Overloaded Methods

All overloaded methods must be explicitly declared as being overloaded. A method is defined as being overloaded by passing the token Overloaded in the method definition.

Class cMyArray is an Array

Function Foo Overloaded returns string

:

End_Function

Function Foo Overloaded string sValue returns string

:

End_Function

Procedure CallFoo

String sVal

Get Foo to sVal // this calls the first Foo

Get Foo "Name" to sVal // this calls the 2nd Foo

End_Procedure

End_Class

When methods are overloaded, the number of parameters determines which method gets called. Note that attempting to call an overloaded method with the wrong number of parameters will generate an error. For example,

// This will generate a compiler error

Get Foo "Name" "Name2" to sVal2

Overloaded Methods with different numbers of parameters are treated as different methods.

It is valid (and quite common) to call one overloaded method from within another one. This provides a way to support different number of parameters within a message. For example:

Class cMyArray is an Array

Procedure Foo Overloaded

// if no parameter is passed, provide a default

Send Foo "Default Name"

End_Procedure

Procedure Foo Overloaded string sVal

Showln "Value is " sVal

End_Procedure

Procedure CallFoo

Send Foo // will show "Default Name"

Send Foo "My Name" // will show "My Name"

End_Procedure

End_Class

Note that the overloading is based on number of parameters. If two methods have the same number of parameters but different types, they are considered the same.

Class cMyArray is an Array

// this Foo will never get called

Procedure Foo Overloaded integer iVal

End_Procedure

Procedure Foo Overloaded string sVal

End_Procedure

End_Class

Visual DataFlex supports overloading of class methods (Functions and Procedures). Property names can not be overloaded. Global functions and procedures, which are not really considered to be methods, cannot be overloaded.

An application may contain overloaded methods and regular (non-overloaded) methods.

Overloaded and non-overloaded methods may not share the same name. The technique for resolving these types of methods is very different. The compiler and runtime must know what kind of message it is trying to resolve. If you attempt to create an overloaded method that has already been defined as non-overloaded you will receive a compiler error. If you attempt to non-overloaded method that has already been defined as overloaded, you will receive a compiler error.

Class cMyArray is an Array

Procedure Foo

end_Procedure

// this will generate a compiler error

Procedure Foo Overloaded

End_Procedure

Procedure Goo Overloaded

End_Procedure

// this will generate a compiler error

Procedure Goo

End_Procedure

End_Class

For more information about declaring methods see:

Declaring Procedure Methods

Declaring Procedure Set Methods

Declaring Function Methods

For more information on calling methods see:

Calling Procedure Methods

Calling Procedure Set Methods

Calling Function Methods


Private Objects

Visual DataFlex classes can be defined to contain nested objects of any other class. Each time an instance of the new class is created the nested objects are also created. Nested objects are useful tools for defining classes that have sophisticated behavior and data storage requirements.

Nested objects that are part of a class's definition become private attributes of the class. That is, they are part of the class's internal design. Whenever an object is destroyed any nested objects it defines are also automatically destroyed.

Nested objects are declared in the class's constructor method (Construct_Object). Below is an example class definition that contains a nested object:

Class cStorageForm is a Form

Procedure Construct_Object

Forward Send Construct_Object

Object oStorageArray is an Array

End_Object

End_Procedure

Procedure DoAppendValue

Integer iItemCount

String sFormValue

Get Value of Self 0 To sFormValue

Get Item_Count of oStorageArray To iItemCount

Set Array_Value of oStorageArray iItemCount To sFormValue

End_Procedure

Procedure DoClearStorage

Send Delete_Data To oStorageArray

End_Procedure

Function StorageValue Integer iItem Returns String

String sReturnValue

Get String_Value of oStorageArray iItem To sReturnValue

Function_Return sReturnValue

End_Function

End_Class

The above example defines a new Form class cStorageForm that contains a nested array object. Three methods are defined as an interface to the nested array. DoAppendValue retrieves the Form object's value and appends it to the nested array; DoClearStorage clears the data in the nested array and StorageValue returns an indexed value from the array.

The methods defined in this example call methods from the current object and from the nested array object. For the purpose of clarity the Self keyword has been used when calling a method of the current object. However, this is redundant since the current object is the default recipient of any message.


The Self Keyword

The Self keyword is defined as a reference to the current object (current instance of the class). Wherever a statement requires a handle to some object the Self keyword can be used when you wish to reference the current object.

The current object is whatever object is currently executing code. For example, if you click on a Button object it will execute its OnClick method. During execution of the OnClick method that particular Button is the current object.


Multiple-Inheritance

Each class that you declare automatically inherits all of the components of its superclass. Visual DataFlex also supports the ability to import the methods of other classes that are not part of the superclass.

A class declaration imports the methods of a class (other than its superclass) by executing an Import Protocol statement in its class definition. The syntax of the import protocol statement is shown below:

Import_Class_Protocol {imported-class}

Where:

  • {imported-class} is the name of the class whose attributes are being imported. For more information on Import Protocol statements refer to the Import_Class_Protocol command in the Visual DataFlex Language Reference.

An example of a class definition with multiple inheritance is shown below:

Class cMessage_Mixin is a Mixin

Procedure Define_cMessage_Mixin

Property String psMessage ""

End_Procedure

Procedure DoShowMessage

String sMessage

Get psMessage To sMessage

Send Info_Box sMessage

End_Procedure

End_Class

Class cMessageButton is a Button

Import_Class_Protocol cMessage_Mixin

Procedure Construct_Object

Forward Send Construct_Object

Send Define_cMessage_Mixin // important!!!

End_Procedure

Procedure OnClick

Send DoShowMessage

End_Procedure

End_Class

The above example declares two new classes. The first one cMessage_Mixin is designed specifically to be mixed into the class definition of some other class using the multiple-inheritance model. Classes designed for this purpose are called Mixin classes (they are based on the Visual DataFlex Mixin class). You would never instantiate objects of any Mixin class, they are designed only as building blocks for other classes.

The second class in the example cMessageButton is based on the button class but it also imports all methods from the cMessage_Mixin class. Notice that the constructor method of this class calls the inherited Define_cMessage_Mixin method. This is important because it demonstrates how properties can be defined in a Mixin and then imported. Properties defined in the constructor of a Mixin class would not get imported because the Mixin's constructor is never called.

The way that Visual DataFlex works is to only import the methods that are directly defined in the class that is being imported. Thus the imported class's own inherited methods are not imported, neither are it's property definitions or private objects. Strictly speaking this is not multiple-inheritance. Instead it can be thought of as importing the skills of another class.

Importing class skills (or methods) is less complicated and more useful than full multiple-inheritance because it eliminates the possibility of an unwanted clash of method definitions. Such a clash would otherwise occur when the superclass of the imported class contains a method or property that is also defined in the superclass of the new class that you are creating.


Objects

An object is an instance of a class. It is a dynamically allocated block of memory with a layout that is defined by its class type. To utilize the functionality of a class you must instantiate it as one or more objects.

Read more about this in these Objects subtopics:

  • Object Declaration

  • Object Nesting

  • Sending Messages to Objects

  • Dynamic Objects


Object Declaration

Objects are instantiated by executing an object declaration statement. The syntax of the object declaration statement is:

Object {object-name} is a {class-name}

{property-settings}

{nested-object-declarations}

End_Object

Where:

  • {object-name} is the name of the new object and

  • {class-name} is the class that the object is derived from.

You may initialize any properties that belong to the object's class inside the object declaration block. Object declarations can also be nested allowing you to declare one object inside another. Refer to the section on Object Nesting for more information.

An example of an object declaration is:

Object oOK_Button is a Button

Set Location to 20 90

Set Label to "OK"

End_Object

The above example declares an object of the Button class and initializes the Location and Label properties. The Visual DataFlex naming convention for objects is to prefix the object name with the letter 'o'. This will help you to identify and separate object names from other identifiers in large applications.

Declaring new properties and methods in an object is a very useful feature of the Visual DataFlex language. It is quite common that each instance of a class will need to have slightly different attributes and/or methods. To declare a new class for each slight variation is quite tedious. Another way would be to design more flexible classes but the disadvantage here is that this flexibility often introduces unwanted complexity.


Declaring New Properties

Visual DataFlex supports the creation of new properties inside an object declaration statement. These properties are referred to as object-properties. Object-Properties are unique to the object they are being declared in and are not shared by other objects of the same class. The following example demonstrates how you can define a new property within an object declaration statement:

Object oOK_Button is a Button

Set Location to 20 90

Set Label to "OK"

Property Integer piClickCount 0

End_Object

Refer to Properties in the Classes section for more information.


Declaring New Methods

In addition to creating object-properties Visual DataFlex supports the creation of new methods inside an object declaration statement. These methods are unique to the object they are being declared in and are not shared by other objects of the same class. The following example demonstrates how you can define a new method within an object declaration statement:

Object oOK_Button is a Button

Set Location to 20 90

Set Label to "OK"

Property Integer piClickCount 0

Procedure OnClick

Integer iClickCount

Forward Send OnClick

Get piClickCount To iClickCount

Set piClickCount To (iClickCount + 1)

End_Procedure

End_Object

In the above example the class's OnClick method is augmented to increment an integer property.

Refer to Methods in the Classes section for more information.


Forward Declaring Objects

Objects can be forward declared using the Register_Object statement. Forward declaring allows you to reference an object somewhere in your program before it is formally declared. The compiler would generate an error in some cases if you tried to do this without a forward declaration.

The syntax for forward declaring an object is

Register_Object {object-name}

Where:

  • {object-name} is the name of the object that you wish to forward declare.


Object Nesting

Object declarations in Visual DataFlex can be nested allowing you to declare one object inside another. The outer object is referred to as the parent and the inner objects are its children. The parent object is said to contain its children.

Object nesting is analogous to models of things that we see in the real-world. For example the structure of a car. A car can be thought of as a compound object that is constructed from smaller component objects, such as 4-wheels, a chassis, engine, body and so on. Each of these smaller components is also made up from other objects; an engine contains pistons, a crank, valves, etc.

A real-world example of a compound object

In terms of a computer application, let's imagine an order entry application. A typical Order Entry application is composed of many distinct modules; a customer maintenance view, an order entry view; a print orders view; an inventory maintenance view and so on. Each one of these views contains multiple identifiable entities. A customer maintenance view, for example, will contain buttons, edit forms, labels, tabbed forms, combos, etc.

A customer entry view demonstrating nested entities

Visual DataFlex represents this structure by nesting objects of different types inside each other. The structure of objects that you create in an application will be determined by its design specification. Below is the Visual DataFlex object structure that was used to define the customer entry view shown above.

Object Customer is a dbView

Set Label to "Customer Entry View"

Set Size to 162 283

Object Customer_DD is a Customer_DataDictionary

End_Object

Set Main_DD to Customer_DD

Set Server to Customer_DD

Object Customer_Number is a dbForm

Entry_Item Customer.Customer_number

Set Label to "Customer Number:"

Set Size to 13 42

Set Location to 5 72

End_Object

Object Customer_Name is a dbForm

Entry_Item Customer.Name

Set Label to "Name:"

Set Size to 13 186

Set Location to 20 72

End_Object

Object CustTD is a dbTabDialog

Set Size to 105 265

Set Location to 36 7

Object Address_TP is a dbTabPage

Set Label to "Address"

Object Customer_Address is a dbForm

Entry_Item Customer.Address

Set Label to "Street Address:"

Set Size to 13 180

Set Location to 8 62

End_Object

Object Customer_City is a dbForm

Entry_Item Customer.City

Set Label to "City/State/Zip:"

Set Size to 13 84

Set Location to 24 62

End_Object

Object Customer_State is a dbComboForm

Entry_Item Customer.State

Set Size to 13 32

Set Location to 24 152

End_Object

// etc.

End_Object

Object Balances_TP is a dbTabPage

Set Label to "Balances"

Object Customer_Credit_Limit is a dbForm

Entry_Item Customer.Credit_limit

Set Label to "Credit Limit:"

Set Size to 13 48

Set Location to 9 72

End_Object

// etc.

End_Object

Object Comments_TP is a dbTabPage

Set Label to "Comments"

Object Customer_Comments is a dbEdit

Entry_Item Customer.Comments

Set Size to 60 242

Set Location to 15 9

End_Object

End_Object

End_Object

End_Object

Note that this example uses the Entry_Item command. Refer to the Visual DataFlex Language Reference for more information on this command.


Desktop Object

The object nesting topic explains how an application’s object structure is determined by the placement of its objects. All objects that you declare must be placed inside of other objects.

To enable you to declare objects outside any other object declarations a special outermost wrapper object is created for you automatically when an application is initialized. This is called the desktop object. This object, based on the cDesktop class contains all of the objects you create.

When an object is not placed inside of any other object, it is still inside of the desktop object. This is sometimes referred to as placing an object “on the desktop”. Objects placed on the desktop are direct children of the desktop object. Objects placed inside of other objects still descendant of the desktop object (grand-child, great grand-child, etc.). Therefore, messages sent to any object are able to delegate to the desktop. For more information refer to the section on delegation.

Desktop Methods

The desktop object is derived from the cDesktop class. To create methods for the desktop object you must use the keyword Desktop in the method declaration. For example:

Procedure DesktopProcedureName Desktop integer iParam string sParam

The desktop keyword signifies that the method is added to the cDesktop class and is therefore understood by the desktop object. It does not matter where this method is defined, if the Desktop keyword is used it is placed in the desktop object.

Messages for the desktop methods can be sent to the current object and resolved via delegation, or they can be sent directly to the desktop object.

// resolve the message via delegation

Send DesktopProcedureName iParam sParam

// send message directly to the desktop (preferred)

Send DesktopProcedureName of Desktop iParam sParam

If you know that the message will be resolved at the desktop, you should use the of desktop syntax as this insures that the method will not be understood and intercepted by some other object in the delegation path and it better shows the programmer’s intent.

Desktop methods should not be overused. Care should be taken when creating methods for the desktop object.

  • Because desktop objects are accessible by all objects they can defeat the purpose of encapsulation.

  • Desktop methods can be scattered all over your code making them more difficult to maintain. This is another reason why they should not be overused.

  • You should never augment or replace an existing desktop method.

  • If you need to create a library of methods that can be used by all objects it is usually better to create objects on the desktop that contain those methods and send message to that object.

Ambiguous Desktop Methods

All procedures and functions defined outside of all other objects should be designated as being Global or Desktop. Prior versions of Visual DataFlex supported a syntax where an outer procedure/ function designation was unspecified. This is referred to as an Ambiguous Desktop method. You should never create an Ambiguous Desktop Method.


Delegation

The Visual DataFlex Send, Set and Get statements instruct an object to execute a method. We say that the object receives a message (or instruction) to execute a method. If the object does not have a definition for the method then the message will be automatically passed to the object's parent. This is repeated until either the message is resolved or the message is passed to the outer-most parent and is still not resolved. When a message is unresolved in this way an error is raised.

The process of passing an unresolved message on to the parent object is called delegation.

The following is an example of object design that takes advantage of message delegation.

Object oButtonPanel is a Panel

Set Size to 50 70

Procedure DoClose

Send Info_Box "Goodbye!"

Send Deactivate

End_Procedure

Object oButton1 is a Button

Set Location To 10 10

Set Label To "Button 1"

Procedure OnClick

Send Info_Box "I am button #1"

Send DoClose

End_Procedure

End_Object

Object oButton2 is a Button

Set Location To 30 10

Set Label To "Button 2"

Procedure OnClick

Send Info_Box "I am button #2"

Send DoClose

End_Procedure

End_Object

End_Object

When clicked, both buttons send DoClose to the current object (themselves). However neither button has a definition for the DoClose method therefore this message would be delegated and resolved by the parent object oButtonPanel.

Note that the objects in the above example define methods as part of the object definition rather than creating a sub-class. This was done for simplicity and to demonstrate how delegation occurs more clearly.

Explicit Delegation

You can explicitly delegate a message to the current object's parent by executing a Delegate statement. An explicitly delegated message directly instructs the current object's parent to execute a method. If this method is not resolved in the parent then the normal flow of message delegation will continue to the grand-parent object and so on.

All three method types can be explicitly delegated (Procedure, Procedure Set, and Function). The syntax for delegating a message is different for each type of method as outlined below:

Delegating Procedure Methods

Delegate Send {method-name} {Param1 … ParamN}

Delegating Procedure Set Methods

Delegate Set {method-name} {Param1 … ParamN} To {value1 … valueN}

Delegating Function Methods

Delegate Get {method-name} {Param1 … ParamN} To {receiving-variable}

You can see that the syntax for delegating a message is the same as the syntax for executing a method except that the Delegate command is appended to the front of each statement. There is also no object reference when delegating a method. You are always delegating the message to the parent of the current object.

The following is an example of object design that takes advantage of explicit message delegation.

Object oPanel is a Panel

Set Size to 50 70

Object oButton1 is a Button

Set Location To 10 10

Set Label To "Button 1"

Procedure OnClick

Send Info_Box "I am button #1"

Delegate Send Deactivate

End_Procedure

End_Object

Object oButton2 is a Button

Set Location To 30 10

Set Label To "Button 2"

Procedure OnClick

Send Info_Box "I am button #2"

Delegate Send Deactivate

End_Procedure

End_Object

End_Object

In the above example, the "Delegate Send Deactivate" statement ensures that it is the object oPanel that executes the Deactivate method. If this message was not delegated then each button would try to deactivate itself when it was clicked rather than deactivate the whole panel since the Button class also understands the Deactivate method.

Refer to Methods in the Classes section for more information on calling methods.


Object Neighborhoods

Visual DataFlex supports the concept of object neighborhoods. Object neighborhoods make it easier for objects to communicate with each other. Objects within a neighborhood can send messages to other objects within the neighborhood without needing to worry about where those objects are declared.

Certain Visual DataFlex classes automatically act as neighborhood containers. All objects nested inside a neighborhood container belong to that container's neighborhood.

Object neighborhoods are analogous to real life compound objects. Our previous example of a car object is composed of compound objects, such as an engine, 4-doors, gearbox, 4-wheels, etc. We can think of all of the sub-objects that make up an engine as belonging to the engine object's neighborhood. This condition reflects the fact that objects within the engine are often interrelated in ways not necessarily connected to their nesting structure.

A real-world example of an object neighborhood

In terms of a computer application, the compound objects of an order entry system such as; a customer maintenance view, an order entry view; a print orders view; and so on are natural object neighborhood candidates. For example a button object might be nested within a group within a tab page within a tab dialog within the customer maintenance view. When clicked, this button may need to send a message to another object. Object neighborhoods would allow the button to send the message without needing to worry about the nesting structure of the recipient, as long as the receiving object lies within the same neighborhood.

For more information refer to Sending Messages Within an Object Neighborhood.


Sending Messages to Objects

As already described the standard syntax for sending messages are:

Send {method-name} [of {object-ID}] {param1 … paramN}

Get {method-name} [of {object-ID}] {param1 … paramN} To {receiving-variable}

Set {method-name} [of {object-ID}] {param1 … paramN} To {setting-variable}

Where:

  • {object-ID} is a handle to the object that will receive and execute the method. There are several ways to obtain the handle of an object.


Sending a Message to Yourself

Often an object will send a message to itself. This means that the object that is sending the message is also the object receiving the message. When this occurs the predefined keyword Self can be used to identify the object ID. In the following example, all messages within an object based on this class are sent to itself, so Self is used to identify the object-ID of each message.

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property Integer piClickCount 0

End_Procedure

Procedure DoAction

Integer iCount

Get piClickCount of Self to iCount

Move (iCount+1) to iCount

Set piClickCount of Self to iCount

End_Procedure

Procedure OnClick

Send DoAction of Self

End_Procedure

End_Class

Because this type of message sending occurs so often, the {of self} syntax may be omitted. When an object-ID is not identified, the current-object (self) is assumed. Therefore, the above example could be more easily written as:

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property Integer piClickCount 0

End_Procedure

Procedure DoAction

Integer iCount

Get piClickCount to iCount

Move (iCount+1) to iCount

Set piClickCount to iCount

End_Procedure

Procedure OnClick

Send DoAction

End_Procedure

End_Class


Sending a Message to Another Object

When a message is sent from one object to another the destination object’s handle must be obtained. This is done by moving the object’s ID to a handle and using that variable as the object-id.

Move oSomeObject to hoHandle

Send MyMessage of hoHandle

For example:

Procedure DoAction

Handle hoButton1

Handle hoButton2

String sLabel1

String sLabel2

Move oButton1 to hoButton1 // contains object ID of button1

Move oButton2 to hoButton2 // contains object ID of button2

Get Label of hoButton1 to sLabel1

Get Label of hoButton2 to sLabel2

Send GenerateNewLabel of hoButton1 sLabel1

Send GenerateNewLabel of hoButton2 sLabel2

End_Procedure

In the above example, object handles were moved to a variable and the variable was then used in the message. The Move command is used to move the object’s handle to the variable. This command actually generates an internal expression that generates a handle for the object and assigns it to the variable. The method used to generate this handle will be discussed shortly.

Alternately, you can reference the object name directly within the message bypassing the need to create a handle variable. This sample could have been re-written as:

Procedure DoAction

String sLabel1

String sLabel2

Get Label of oButton1 to sLabel1

Get Label of oButton2 to sLabel2

Send GenerateNewLabel of oButton1 sLabel1

Send GenerateNewLabel of oButton2 sLabel2

End_Procedure

This method requires less code and is easier to read.

While this second method makes it easier to write code there is an optimization issue that must be understood. When an object name is used directly within a message, it’s handle must be evaluated each time it is used (just like the object handle must be evaluated in the Move command). This evaluation is not as efficient as using a handle variable. So, if the same object is going to be accessed many times within the same method, you will probably want to move the object handle to a variable and then use the variable to send the message. This way the object handle only needs to be evaluated one time. For example:

Procedure DoAction

Handle hoArray

Integer iCount

// Non-Optimized: this method will be slower

Send delete_data of oMyArray1

For iCount from 0 to 10000

// oMyArray must be evaluated each time it is used

Set value of oMyArray1 iCount to "Test"

Loop

// Optimized: this method will be slightly faster.

Move oMyArray2 to hoArray // oMyArray2 is evaluated once

Send delete_data of hoArray

For iCount from 0 to 10000

Set value of hoArray iCount to "Test"

Loop

End_Procedure

The optimization involved here is quite small. In most cases, you will not be able to perceive a performance difference between the two techniques.


Passing Object Handles as a Parameter

The discussion of object IDs have been limited to using them to determine which object receives a message. In some cases, you may need to pass an object handle as a parameter within a message. In such a case, you must use the move command to move the object ID to a handle variable and pass the variable. You may not pass the object name directly.

The following example shows the right way and the wrong way to pass an object handle as a parameter. In this example we are trying to send a message to oButton1 passing as a parameter the object handle of oButton2.

// this is the correct way

Procedure GoodAction

Handle hoButton2

Move oButton2 to hoButton2 // first move object handle to variable

Send NotifyButton of oButton1 hoButton2 // then, pass the variable

End_Procedure

// this will NOT work. You will get a compile error…

Procedure BadAction

Send NotifyButton of oButton1 oButton2

End_Procedure


Relative Object Name Access

The object access method described above is relative. An object handle is determined by an object’s name relative to the object that sent the message. The use of relative object addressing makes it possible to maintain proper encapsulation while at the same time providing an object access scheme that is easy to code and easy to maintain. Relative object addressing is an important part of the Visual DataFlex object model and should be thoroughly understood.

Good object-oriented programming style states that an object should only need to know about objects that exist within its immediate environment or scope. An object can be aware of itself, its immediate children, its parents (ancestors) and other objects declared within the same neighborhood. If an object needs to communicate with an object that is “out of its reach”, your object message interface should be changed. For example, if an object needs to communicate with an object in another neighborhood you should change your interface so that the object can send a message to the neighborhood's container object (which is within its scope). The container would then send a message to its child (which is in its scope).

The Visual DataFlex object accessing method and neighborhoods allow you to directly access all objects that you should be able to access. When an object handle is requested, through either a move command (move oMyObj to hoMyObj) or as part of message (Send MyMgs of oMyObj) the following steps are taken to find the named object and return its handle Id.

  1. The object sending the message looks for a child object with the target name. If an object is found, the object’s handle is returned.

  2. If the sending object belongs to an object neighborhood then the list of reachable objects within the neighborhood is searched for an object with the target name. If an object is found, the object's handle is returned.

  3. The object sending the message checks to see if there is a properly named sibling object. If found, a handle is returned. At this point it is actually looking at the parent’s child-objects.

  4. The object sending the message will now look at all child objects belonging to its grandparent object (siblings of the parent). If found, the handle is returned.

  5. This process of moving up to an ancestor object and checking all children continues until an object is found, or the outer-most parent is tested and the target is still not found, in which case a handle value of zero is returned. This delegation takes place regardless of whether the parent is in the sending object's neighborhood.

This means that an object’s scope consists of:

  • itself

  • its children

  • its neighborhood

  • its ancestors (parents, grand-parents, etc.)

  • all sibling objects of all ancestors.

Consider the following object structure:

Object oArray1 is an Array

End_Object

Object oArray2 is an Array

Object oArray3 is an Array

End_Object

End_Object

Object oPanel is a Panel

Object oContainer1 is a Container3d

Object oButton1 is a Button

End_Object

Object oButton2 is a Button

End_Object

End_Object

Object oContainer2 is a Container3d

Object oButton3 is a Button

End_Object

Object oButton4 is a Button

End_Object

End_Object

End_Object

Object oArray1 has no parents and no children. Therefore, it can send messages to itself and its siblings, oArray2 and oPanel. Similarly, oArray2 can send messages to itself and oArray1 and oPanel.

Object oPanel has children but no parents. Object oPanel is a neighborhood container (all panel objects are neighborhood containers). Therefore, it can send messages to itself, all object nested within itself, oArray1 and oArray2 but not oArray3. All objects within oPanel belong to the same neighborhood and therefore can send messages the same objects that are accessible by oPanel.

It is important that all objects within a particular neighborhood have unique object names. Name clashes within a particular neighborhood may result in the wrong objects being referenced.

Note that the oArray2 object contains an array object (oArray3). Object oArray3 is not accessible from any object except for oArray2. Similarly, none of the array objects may access objects nested within oPanel since they do not belong to oPanel's neighborhood.

Relative object access means that when you send messages to objects you will always use the object’s relative name (e.g., oButton1). If the object is found, the object is in scope and your program is proper. If the object is not found, the object is out of scope and your program needs to be corrected.

This relative object access method is one of the cornerstones of the Visual DataFlex Application Framework (which is discussed in other documents). This method is used to create views, create and connect data-dictionary object structures, and create nested data-entry objects that all properly and elegantly connect to the proper data-dictionary objects.


Sending Messages within an Object Neighborhood

Visual DataFlex supports the concept of object neighborhoods. The purpose of this is to make it easier for objects to communicate with each other. Objects within a neighborhood can talk to other objects within the neighborhood by the first name (their simple object name) without needing to worry about where those objects are located.

Object Neighborhoods make it easier to manage visual objects within views. In a view, visual controls are often nested inside containers for appearance purposes only. As you design an application you may decide that it looks better to place several objects within dbGroups and that it then looks better to place those groups within a dbContainder3d. Without object neighborhoods, every time you move these objects around and put them in different containers you need to change the way those objects are referenced from other objects within the view. This creates the need to generate complex object reference statements and it may require code change every time you move objects around resulting in code that looks like this:

Get SortOrder of (oSortOrder(oSortParamsGroup(oSetUpContainer ;

(oTabPageReport(oTabDialog(self)))))) to iOrder

And, if you decide to move your oSortOrder object somewhere else in the view, you need to start all over.

With neighborhoods all objects in the view are registered with the view and all objects within the view are visible to all other objects within the view. The same object placed anywhere within the view is now coded as:

Get SortOrder of oSortOrder to iOrder

In Visual DataFlex all views and modal panels are defined as public neighborhoods.

Object neighborhoods "flatten" the object structure for all appropriate objects within the neighborhood. For this reason it is important that all objects within a given neighborhood have unique object names.

Objects can also be explicitly excluded from a neighborhood by setting a special property. This is useful as there are times when you would have special objects that you would not wish to be made public to the rest of the neighborhood. This allows easy access to the objects you want easy access to while still writing properly encapsulated applications.

Expected Use of Neighborhoods

All panel objects are defined as being public neighborhoods by default. This includes views and modal dialogs. All objects declared within a public neighborhood will be members of the neighborhood. They can all be accessed from any other member object by their short object name.

All objects contain a property named peNeighborhood. If you wish to make a panel private you can change the value of peNeighborhood from nhPublic to nhPrivate. Making a panel private will switch off the neighborhood effect within that panel object.

How Neighborhood Object Referencing Works

When an object is created it will try to register itself with a neighborhood. It does this by checking with its parent object by looking at the parent property peNeighborhood. That property will either define the parent as being a neighborhood object (nhPublic or nhPrivate) or it will tell the object to delegate and check with its parent (nhNo). This process will continue until a neighborhood object is encountered. Once found that neighborhood object will either be public (nhPublic) or private (nhPrivate). If the object is public, it will register this new object as a member of that neighborhood, allowing all other objects in the neighborhood to access it with its short name. If the object is private, the object is not registered. In either case, the search has ended and the newly created object is either part of a neighborhood or it is not.

This newly created object will have its own peNeighborhood property setting. This does not affect how it is registered but it does determine how its child objects will be registered when those objects are created. In other words, an object's parent determines its membership in a neighborhood.

An object name is found using the following rules:

  1. First check to see if oName is a child object. If found, use that object

  2. Check if the object is a public neighborhood. If so, mark as " neighborhood found" and see if the object is in the neighbor list. If found use that object

  3. Delegate and see if oName is a child of the parent object. If found use that object

  4. If a neighborhood has not yet been found, check if the object is a public or private neighborhood. If so, mark as "neighborhood found". If public, see if the object is in the neighbor list. If found use that object

  5. Continue step 3 until there are no more parent objects in which case issue an object not found error

Only one public neighborhood is ever checked. This means that any object may only belong to one neighborhood. (Supporting membership in multiple nested neighborhoods would only create confusion.)

Property peNeighborhood

As indicated, all neighborhood behavior is controlled by the peNeighborhood property. All objects support this property. Three modes are supported:

  1. nhPrivate – If an object is private it means that all descendant objects (all objects within this object) do not support neighborhood referencing. You can think of these descendent objects as not being part of any neighborhood or you can think of them as being part of a private neighborhood where they just don't like to talk to each other. Non-neighborhood object access methods are still used (check if name exists as a child, if not delegate to parent and repeat search).

The actual object marked as nhPrivate may be part of a neighborhood, but it descendents are not. An object's neighborhood status is determined by its parent.

Most objects in Visual DataFlex are private. All visual controls (e.g. form, dbForm) and all non-visual objects (e.g. Array, XML objects) are private. In addition, objects created as part of class construction are usually private.

  2. nhPublic – If an object is public it maintains a list of all of its descendents. When an object name is requested of this object either directly or via object access name delegation it will search its list to see if the object exists. Views and Modal panels are defined as being public. Using this value defines a neighborhood.

  3. nhNo – This mode indicates that this object does not define a neighborhood either public or private. Instead it allows its descendents (all objects within this object) to ignore this object and check with its parent to see what its neighborhood status might be. This is the mode that is used by visual containers like groups and container3d. It is the mode that flattens the object structure for visual containers.

We don't expect you will ever have to change this property, with perhaps the one exception of changing a view or modal panel from public to private. Even in this case we would encourage you research the need for this change and to see if you can change your application to fully support neighborhoods.

Duplicate Object Names and Neighborhoods

Duplicate object name are allowed within a neighborhood, but only one of those names will be found when the neighborhood list is searched. If you are planning on resolving an object name via a neighborhood you should avoid using duplicate names. Typically this will not be a problem. When duplicate names are created within a view, the program rarely needs to find those objects by name – otherwise you would have assigned the object a unique name. Either you are never sending messages to these objects or you are creating them dynamically in which case you should be accessing them by their object handle ID and not their name.

Also note that a number of classes create objects as part of class construction. You would rarely want to make these objects public, and in fact they will not be made public. If the object is private these child objects remain private - this encompasses most objects. Only visual containers are non-private and even in these cases they actually are marked private until the end of construct_object. In this case all the children defined during construction (those objects defined within the class) remain private.


Long and Short Object Names

Each object has a long name and a short name. The short name is its relative name and is the name that you will use to access objects. This name is stored in a property called Object_Label. Any object’s relative name can be retrieved with the following code:

Get Object_Label to sName

Examples of short object names are:

oArray1

oPanel

oContainer1

oButton1

An object’s long name consists of its short name and all of the short names of its ancestor objects. This name is stored in a property called Name. Any object’s long name can be retrieved with the following code:

Get Name to sName

Examples of long object names are:

oArray1

oPanel

oPanel.oContainer1

oPanel.oContainer1.oButton1

oPanel.oContainer2.oButton1.oArray1


Other Object Access Methods

As you look through sample code and package code you will see that objects are often accessed using an object access expression syntax. This syntax has the format of:

(objectName(Self)) or (ObjectName(Current_Object))

This is an older style of coding that, while still valid, has been replaced with the simpler methods discussed here. The following two code segments generate the same compiled code:

Send MyMessage of (oMyObject(Self))

Get MyProperty of (oMyObject(Self)) to iVar

Move (oMyObject(self)) to hObj

Move (Label(oMyObject(Self))) to sLabel

and

Send MyMessage of oMyObject

Get MyProperty of oMyObject to iVar

Move oMyObject to hObj

Move (Label(oMyObject)) to sLabel

The expression syntax must be used if you insist on accessing objects that are outside of an object’s neighborhood.

Move (oRadio1(oRadioGroup(oMyView))) to hoRadio1

The syntax can also be used to access an object by its absolute location:

Move (oRadio1(oRadioGroup(oMyView(oClientArea(oPanel(desktop)))))) to hoRadio1

The complexity of this code speaks for itself. It should and can be avoided. For more information regarding expression syntax refer to Reading Properties using Expressions .

For more information regarding object neighborhoods refer to Object Neighborhoods.


Dynamic Objects

Typically objects are created when the object / end_object command block is executed. Once created those objects exist until the program terminates. Under some conditions you may wish to create and destroy objects dynamically. Visual DataFlex supports methods for doing this. Dynamic object creation and destruction is typically used inside of methods (procedures and functions) that need to temporarily create an object, work with it and then dispose it when no longer needed. For example, this type of dynamic object management is used extensively when parsing XML documents. Two methods are used to handle dynamic objects: create and destroy.

Object Creation

The syntax for dynamically creating an object is:

Get Create [of {object-name}] {class-id} to {object-handle}

Where:

  • {object-name} is a reference to an object that will receive the Create message.

  • {Class-id} is a special symbol that represents the class of the object to be created. The class-Id symbol is simply the class name prefaced with “U_”. For example, the class-Id for a button class is U_Button.

  • {object-handle} is a handle variable identifier that will receive a handle to the newly created object.

The Create method is passed a class-Id. It creates an object based on the class-Id and returns the new created object’s handle. The object is created as a child of the object that receives the create message.

The create method returns a handle to the object. During the lifetime of the object it will be referenced by its handle. Dynamically created objects do not have meaningful names.

Object Destruction

The syntax for dynamically destroying an object is:

Send Destroy [of {object-name}]

Where:

  • {object-name} is a reference to the object to be destroyed.

The destroy message destroys the object that receives the message. It also destroys any child objects contained within the object. When an object is destroyed all resources used by the object are freed. The Destroy message can be used to destroy any object but is used most frequently to destroy dynamically created objects.

Examples

This example creates an array object, uses it, and then destroys it.

Procedure ProcessData

Handle hoData

Get create U_Array to hoData

Send DoLoadData hoData

Send DoUpdateData hoData

Send Destroy of hoData

End_Procedure

This example could also have been coded using the object / end_object syntax as follows:

Procedure ProcessData

Handle hoData

Object oTempArray is an Array

Move self to hoData

end

Send DoLoadData hoData

Send DoUpdateData hoData

Send Destroy of hoData

End_Procedure

Typically dynamic objects are created using the create method.

The following example shows a more complicated example of working with dynamic objects to create an XML document.

Function CustomerXMLList Returns XmlHandle

Integer bOk

Handle hoXML hoRoot hoEle hoXML1 hoRoot1

Handle hoCustomerDD

String sName sNumber sState sNamespace

Move Customer_dd to hoCustomerDD

// namespace to use for document

Move "http://www.dataaccess.com/Test/CustomerList" to sNameSpace

// create XML document / Create root node

Get Create U_cXMLDomDocument To hoXML

// Create the Root element named CustomerList

Get CreateDocumentElementNS Of hoXML sNameSpace "CustomerList" To hoRoot

// now go through all customer records

//Send AddAttribute of hoRoot "xmlns:m" sNameSpace

Send Clear of hoCustomerDD

Send Find of hoCustomerDD ge 2

While (found)

// get name, number and state to strings

Move (trim(Customer.Name)) To sName

Move Customer.Customer_Number To sNumber

Move (trim(Customer.State)) To sState

// for each customer create customer node with child elements

Get AddElementNS Of hoRoot sNameSpace "Customer" "" To hoEle

Send AddElementNS Of hoEle sNameSpace "Name" sName

Send AddElementNS Of hoEle sNameSpace "Number" sNumber

Send AddElementNS Of hoEle sNameSpace "State" sState

Send AddAttributeNS of hoEle sNameSpace "num" 1

Send Destroy Of hoEle

Send Find of hoCustomerDD gt 2

Loop

Function_Return hoXML

End_Function

Dynamic object creation can be used to create non-visual and visual objects. Most often it is used with non-visual objects.


Programs

Visual DataFlex programs have a very free format structure. Most of the program is made up of individual source code files called packages. Packages are the basis of modular programming in Visual DataFlex. They are used to create libraries that you can include in various programs, and to divide large programs into logically related modules.

Read more about this in these Programs subtopics:

  • Using Packages

  • Simple Programs

  • Pre-Compilation


Using Packages

Packages are included into the body of a program via the Use Statement. The syntax of the Use statement is:

Use {package-name}

where:

  • {package-name} is the file name of the package file that will be included.

The Use statement is a compiler directive instructing the compiler to merge the source code of the package file into the program at the point where the Use statement occurs. Source code packages can also contain Use statements. The compiler keeps track of each package that is used and will ignore a Use statement if the package has already been included once.

Visual DataFlex also has a #INCLUDE compiler directive for merging external source packages. The difference is that the #INCLUDE command can be used to merge-in multiple copies of the same package file. Refer to the Visual DataFlex Language Reference for more information on the #INCLUDE compiler directive.


Simple Programs

Most Visual DataFlex programs will conform to at least the following basic structure:

Use Windows.pkg

{use all other necessary packages}

{declare global variables}

{declare global functions & procedures}

{define a panel object}

Start_UI

In the above structure the 'Use Windows.pkg' instructs the compiler to include a library of basic definitions that are needed to write a simple Windows program. A Visual DataFlex program will define at least one object. It is necessary to include the class definitions for each object's class. These definitions are stored in external package files. Refer to the section on Using Packages for more information.

The Start_UI statement is required to start the event driven user interface engine. Without a Start_UI the program will simply create all of the defined data structures then exit.

An example of a simple Visual DataFlex program that displays a single panel object is shown below:

Use Windows.pkg

Use DFPanel.pkg

Object oPanel is a Panel

Set Label to "Visual DataFlex"

Set Size to 80 150

End_Object

Start_UI

Refer to the Visual DataFlex Developing Applications Guide for information on how to build large-scale database applications.


Pre-Compilation

Visual DataFlex allows you to mark a section at the top of your program for pre-compilation. Programs that contain a pre-compiled section will compile faster. The main restriction on the pre-compiled portion of your program is that it must be the first part of your program. If any lines of code are placed above the section that you have marked for pre-compilation then the compiler will continue as though none of your program is pre-compiled.

There are two ways to achieve pre-compilation:

  • Pre-compiled packages: You can pre-compile any package file, if the Use statement for a pre-compiled package is on the first line of your program then the compiler will use the pre-compiled code.

  • #HEADER … #ENDHEADER compiler directives: These directives mark a block of code for pre-compilation. You may not use a #HEADER … #ENDHEADER pair to split any compound statement (such as an object declaration).

An example of a program with a block marked for pre-compilation is shown below:

#HEADER

Use Windows.pkg

Use DFPanel.pkg

#ENDHEADER

Object oPanel is a Panel

Set Label to "Visual DataFlex"

Set Size to 80 150

Object oButton Is A Button

Set Location to 40 10

Set Label to "Exit"

Procedure OnClick

Delegate Send Deactivate

End_Procedure

End_Object

End_Object

Start_UI

Refer to the Compiler help for more information on pre-compiling packages and programs.


Sequential File Input and Output

A text file is considered to represent a sequence of characters formatted into lines, where each line is terminated by an end-of-line marker. This section describes the capabilities and techniques for performing sequential I/O to text files in Visual DataFlex.

Read more about this in these Sequential File I/O subtopics:

  • Opening Sequential I/O Files

  • Appending Data to an Output File

  • Reading Data

  • Writing Data

  • Random Access

  • Using explicit I/O channels

  • Closing Sequential I/O Files


Opening Sequential I/O Files

Visual DataFlex can read data from, and write data to, up to ten sequential I/O files simultaneously. Before you can read or write sequential data you must first open the file. The syntax for opening a file for input (reading) is:

direct_input [channel {channel-num.}] {file-name}

And the syntax for opening a file for output (writing) is:

direct_output [channel {channel-num.}] {file-name}

Where:

  • {channel-num.} is the assigned channel for the file that is being opened; and

  • {file-name} is the name and path of the file that is being opened. {file-name} can be a string token, string variable or a string expression.

Examples of opening a file for sequential I/O are:

direct_input "c:\myDocs\Readme.txt"

direct_output sErrorLog

Assigning an explicit I/O Channel

Whenever a file is opened for I/O it is assigned an I/O channel. Visual DataFlex supports up to ten channels for simultaneous sequential file I/O. The channels are numbered 0..9.

The default channel for input is channel 0; the default channel for output is 1.

The channel that you assign to a file when it is opened for sequential I/O must be unused. When you close a file for sequential I/O the channel that was assigned to it becomes available for reuse.

Examples of opening a file for sequential I/O and explicitly assigning a channel number are:

direct_output channel 3 "c:\myDocs\Readme.txt"

direct_output channel iOutChannel "c:\autoexec.bat"

Testing for Valid Input Files

If the file named in a direct_input statement cannot be opened then the Boolean expression Seqeof returns True. The following example demonstrates how you can test for a valid input file name.

direct_input sActivityLog

If (seqeof) Showln "Error: can't open " sActivityLog


Appending Data to an Output File

If the file that is named in a Direct_Output statement already exists then the contents of the existing file are deleted and replaced with whatever data is written to it. To append data to the end of an existing file you should use the Visual DataFlex Append_Output command to open the file. The syntax of the Append_Output command is:

Append_Output [channel {channel-num.}] {file-name}

Where:

  • {channel-num.} is the assigned channel for the file that is being opened; and

  • {file-name} is the name and path of the file that is being opened. {file-name} can be a string token, string variable or a string expression.

Examples of opening files using Append_Output is:

Append_Output sErrorLog

Append_Output Channel 3 "c:\temp\activitylog.txt"


Reading Data

Reading Data

There are four Visual DataFlex commands for reading data from a sequential I/O file, they are: Read, Readln, Read_Block, and Read_Hex. The first three are detailed below. Refer to the Language Reference for more information about each of these commands.

Read

The Read command reads comma-delimited data from the input file starting from the first line of the input file until it finds a comma or end-of-line character. Then it moves the data to a variable. The syntax for the Read command is:

Read [channel {channel-num.}] {variable1} [{variable2 .. variableN}]

Where:

  • {channel-num.} is the channel assigned to the file that is being read;

  • {variable1 .. N} are variables that will be assigned the values that are read in.

If the channel specification is omitted then data is read from whichever channel was last referenced, or channel 0 if no channel has been referenced. Each Read command must specify at least one variable for receiving the read data.

Successive Read statements pick up at the end of the last data that was read, until an end-of-line character is encountered. To read past an end-of-line character in a data file, you must use a Readln command. When an end-of-line character is encountered, the Boolean expression seqeol returns True. When an end-of-file character is encountered, the Boolean expression seqeof returns True. An example of using the Read command is:

Direct_Input "c:\Names.txt"

Repeat

Read sFirstName

Read sLastName

If (Seqeol) Readln

Until (seqeof)

Close_Input

You can specify more than one {variable} per Read command. This is equivalent to executing multiple Read commands, one per {variable}. The Repeat statement in the previous example could be rewritten as shown below:

Repeat

Read sFirstName sLastName

If (Seqeol) Readln

Until (seqeof)

Readln

The Readln command reads data from the input file starting from the first line of the input file it finds an end-of-line character. Then it moves the data to a variable.

The syntax for the Readln command is:

Readln [channel {channel-num.}] [{variable1} .. variableN}]

Where:

  • {channel-num.} is the channel assigned to the file that is being read;

  • {variable1 .. N} are variables that will be assigned the values that are read in.

If the channel specification is omitted then data is read from whichever channel was last referenced, or channel 0 if no channel has been referenced. If no {variable} is specified in the Read command the data is read in, then discarded.

Successive Readln statements pick up at the end of the last data that was read. After each Readln the Boolean expression seqeol returns False. When an end-of-file character is encountered, the Boolean expression seqeof returns True. An example of using the Readln command is:

Direct_Input "c:\Names.txt"

Repeat

Readln sWholeName

Until (seqeof)

Close_Input

You can specify more than one {variable} per Readln command. This is equivalent to executing multiple Read commands, one per {variable} followed by a Readln with no variable.

Read_Block

The Read_Block command reads a specified number of bytes of data from the input file. Then it moves the data to a variable. The syntax for the Read_Block command is:

Read_Block [channel {channel-num.}] {variable} {length}

Where:

  • {channel-num.} is the channel assigned to the file that is being read;

  • {variable} is the variable that will be assigned the data that is read in; and

  • {length} is the number of bytes that will be read.

If the channel specification is omitted then data is read from whichever channel was last referenced, or channel 0 if no channel has been referenced.

Successive Read_Block statements pick up at the end of the last data that was read. When an end-of-file character is encountered, the Boolean expression seqeof returns True. An example of using the Read_Block command is:

Direct_Input "c:\PhotoID.bmp"

Read_Block sPhoto 16000

Close_Input

The Read_Block command makes no distinctions among characters in the file (delimiters, spaces, tabs), except the end-of-file character, which delimits the end of the file. Read_Block is intended for use with non-delimited text or binary-data files.

You must set the length and the pattern of Read_Block statements according to your prior knowledge of the structure of the data in the input file, since it cannot be inferred from delimiting characters. The maximum length is governed by the maximum number of characters that can be stored in the receiving variable (refer to the section on String variables for further details).


Writing Data

There are three Visual DataFlex commands for writing data to a sequential I/O file, they are: Write, Writeln, and Write_Hex. The first two are detailed below. Refer to the Language Reference for more information about each of these commands.

Write

The Write command writes data to the output file. Write appends the data to the output file without creating a new line. A Writeln statement must be executed to advance output to the next line. The syntax for the Write command is:

Write [channel {channel-num.}] {value1} [{value2 .. valueN}]

Where:

  • {channel-num.} is the channel assigned to the file that is being read;

  • {value1 .. N} are the data items that are written. {value} can be any constant, variable or expression.

If the channel specification is omitted then data is written to whichever channel was last referenced, or channel 1 if no channel has been referenced. Each Write command must specify at least one {value} to be written.

An example of using the Write command is:

Direct_Output "c:\Names.txt"

Write "Bill, Bob, Jane"

Writeln

Write "Mary, Stuart, John"

Writeln

Close_Output

Writeln

The Writeln command writes data to an output file then outputs an end-of-line character. The Syntax of the Writeln command is:

Writeln [channel {channel-num.}] [{value1 .. valueN}]

Where:

  • {channel-num.} is the channel assigned to the file that is being read;

  • {value1 .. N} are the data items that are written. {value} can be any constant, variable or expression.

If the channel specification is omitted then data is written to whichever channel was last referenced, or channel 1 if no channel has been referenced. If no {value} is specified then the Writeln command will simply add an end-of-line character to the output file.

An example of using the Writeln command is:

Direct_Output "c:\Names.txt"

Writeln "Bill, Bob, Jane"

Writeln "Mary, Stuart, John"

Close_Output


Random Access

After you open a file for Input the first Read or Readln statement will begin reading at the beginning of the file. Each subsequent Read or Readln will continue where the last read finished. In this way you can progress through the file from start to finish. Similarly when a file is opened for output, data is always appended to the end of the last data that was written to the file.

The Visual DataFlex Set_Channel_Position command allows you to explicitly specify an offset from the beginning of the I/O file where the next read or write operation will occur. The syntax of the Set_Channel_Position command is:

Set_Channel_Position {channel-num.} To {offset}

Where:

  • {channel-num.} is the channel assigned to the file that you are reading or writing; and

  • {offset} is the number of bytes from the beginning of the file that the next I/O operation will occur.

An offset value of 1 specifies that the next I/O operation will occur at the first byte (i.e., the {offset} parameter is not zero-based). An offset value of –1 specifies that the next I/O operation will occur at the end of the file.

Refer to the Set_Channel_Position and Get_Channel_Position commands in the Language Reference for more information.


Using explicit I/O Channels

When more than one channel is used for simultaneous input or output then you must explicitly assign separate channel numbers to each file. The following example demonstrates reading data from three files and writing their combined contents to a fourth.

String sCar sBus sTaxi

Direct_Input Channel 2 "c:\Cars.txt"

Direct_Input Channel 3 "c:\Busses.txt"

Direct_Input Channel 4 "c:\Taxis.txt"

Direct_Output Channel 5 "c:\Vehicles.txt"

// Read from cars.txt

Repeat

Readln Channel 2 sCar

Writeln Channel 5 sCar

Until (Seqeof)

// Read from busses.txt

Repeat

Readln Channel 3 sBus

Writeln Channel 5 sBus

Until (Seqeof)

// Read from taxis.txt

Repeat

Readln Channel 4 sTaxi

Writeln Channel 5 sTaxi

Until (Seqeof)

Close_Input Channel 2

Close_Input Channel 3

Close_Input Channel 4

Close_Output Channel 5

The (seqeof) expression will return the end-of-file status of the last referenced channel number in a Readln command.


Closing Sequential I/O Files

Each I/O file that is opened should be closed when you have finished reading from or writing to it. Closing the file releases the channel for use with another I/O operation. It also makes the file available to be accessed by some other process.

The syntax for closing an input file:

Close_Input [channel {channel-num.}]

And the syntax for closing an output file is:

Close_Output [channel {channel-num.}]

Where:

  • {channel-num.} is the assigned channel of the file that is being closed. If the channel parameter is omitted then the default input or output channels will be closed.


Accessing Class Attributes with Expressions

  • Reading Properties using Expressions

  • Calling Function Methods using Expressions


Reading Properties using Expressions

Visual DataFlex supports two distinct ways to read the Properties of an object. The first syntax is to use a Get statement, the second syntax is to read the property within an expression.

The syntax of the Get statement for reading a property is:

Get {property-name} [of {object-id}] To {variable-id}

Where:

  • {property-name} is the name of the property that is being read; {object-id} is a handle to the object whose property is being read; and {variable-id} is the name of a variable that will receive the property's value.

This syntax is fully discussed in the section on Reading Property Values. The syntax of reading a property using an expression is:

({property-name}({object-id}))

Notice that the {object-id} is a compulsory part of the expression syntax for reading a property. If the object property is in the same object as the one sending the message, the keyword self must be used as the object-id.

Below is an example of using expression syntax to read a property value, followed by the equivalent Get statements:

Move (Label(Self)) To sLabel // self is required

Get Label To sLabel // self is implied

Get Label of self To sLabel // self is explicit (but was not required)

If the object property is in a different object than the object sending the message the object must be named.

Below are some examples of using expression syntax to read property values from another object followed by the equivalent Get statement:

Move (Label(oButton)) To sLabel

Get Label of oButton To sLabel

Sending a Message to Yourself

Often an object will send a message to itself. This means that the object that is sending the message is also the object receiving the message. When this occurs the predefined keyword Self can be used to identify the object ID. In the following example, all messages within an object based on this class are sent to itself, so Self is used to identify the object-ID of each message.

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property Integer piClickCount 0

End_Procedure

Procedure DoAction

Integer iCount

Get piClickCount of Self to iCount

Move (iCount+1) to iCount

Set piClickCount of Self to iCount

End_Procedure

Procedure OnClick

Send DoAction of Self

End_Procedure

End_Class

Because this type of message sending occurs so often, the {of self} syntax may be omitted. When an object-ID is not identified, the current-object (self) is assumed. Therefore, the above example could be more easily written as:

Class cMyButton is a Button

Procedure Construct_Object

Forward Send Construct_Object

Property Integer piClickCount 0

End_Procedure

Procedure DoAction

Integer iCount

Get piClickCount to iCount

Move (iCount+1) to iCount

Set piClickCount to iCount

End_Procedure

Procedure OnClick

Send DoAction

End_Procedure

End_Class

In the above example the Label property of an object called oButton is read into a variable called sLabel.

Alternately, the object handle can be moved to a variable and used in an expression as follows:

Handle hoButton

Move oButton to hoButton

Move (Label(hoButton)) to sLabel

In the following example the Enabled_State property of an object called oButton is evaluated in an If statement.

If (Enabled_State(oButton)) Showln "This button is enabled"

Get Enabled_State of oButton To bEnabled

If (bEnabled) Showln "This button is enabled"

The following example shows how multiple properties may be evaluated within a single expression. Several different coding styles are demonstrated that all accomplish the same goal.

Style A:

If (Changed_State(oCustomer_DD) AND ;

Allow_Update_State(oCustomer_DD) AND ;

(Find_Mode(oCustomer_DD)=MODE_SAVING)) ;

Send DoUpdateStatus

Style B:

Handle hoCustomer_DD

Move oCustomer_DD To hoCustomer_DD

If (Changed_State(hoCustomer_DD) AND ;

Allow_Update_State(hoCustomer_DD) AND ;

(Find_Mode(hoCustomer_DD)=MODE_SAVING)) ;

Send DoUpdateStatus

Style C:

Handle hoCustomer_DD

Integer bChanged bAllowUpdate iMode

Move oCustomer_DD to hoCustomer_DD

Get Changed_State of hoCustomer_DD to bChanged

Get Allow_Update_State of hoCustomer_DD to bAllowUpdate

Get Find_Mode of hoCustomer_DD to iMode

If (bChanged AND bAllowUpdate AND (iMode=MODE_SAVING)) ;

Send DoUpdateStatus

Style A: uses the expression syntax to evaluate each property and is the most concise. It would be less efficient to execute than the other two styles.

Style B: also uses expression syntax, but the object ID has been evaluated first and is passed to the expression in a Handle variable. This style is slightly more verbose but the code is more efficient.

Style C: Is the most verbose. It uses Get statements to retrieve the property values so that they are not evaluated in the If statement. The result is a much simpler If statement. The code would also be more efficient if the properties were used more than once because the values have already been retrieved into variables.


Calling Function Methods using Expressions

Visual DataFlex supports two distinct ways to call Function Methods. The first syntax is to use a Get statement, the second syntax is to call the method within an expression.

The syntax of the Get statement for calling a Function method:

Get {method-name} [of {object-id}] {param1 … paramN} To {variable-id}

Where:

  • {method-name} is the name of the Function method that is being executed;

  • {object-id} is a handle to the object who's method is being executed; and

  • {variable-id} is the name of a variable that is assigned the value that is returned by the Function method.

This syntax is fully discussed in the section on Calling Function Methods. The syntax of calling a Function method using an expression is:

({method-name}({object-id}[,param1 … ,paramN))

Notice that the {object-id} is a compulsory part of the expression syntax for calling a Function method. The function's parameters follow the {object-id}. Each parameter is delimited by a single comma.

If the object being called is the same as the object sending the message, the keyword self must be used as the object-id.

Below is an example of using expression syntax to call a function, followed by the equivalent Get statement:

Move (Calc_Sales_Tax(Self, nAmount)) To nSalesTax // self is required

Get Calc_Sales_Tax nAmount To nSalesTax // self is implied

Get Calc_Sales_Tax of Self nAmount To nSalesTax // optional use of self

If the object being called is a different object than the object sending the message the object must be named. Refer to the section on Other Object Access Methods for more information.

Move (Calc_Sales_Tax(oSalesDD, nAmount)) To nSalesTax

or,

Move oSalesDD to hoSalesDD

Move (Calc_Sales_Tax(hoSalesDD,nAmount)) To nSalesTax

or,

Get Calc_Sales_Tax of oSalesDD nAmount To nSalesTax

Object functions may also be used as part of complex expressions:

Move (Calc_Sales_Tax(oSalesDD, nAmount)/100) To iSalesTax

or,

Get Calc_Sales_Tax of oSalesDD nAmount To nSalesTax

Move (nSalesTax/100) to iSalesTax

The following example passes two parameters to a function.

Move (Field_Option(oCustomerDD, iField, iOption)) To bOption

Get Field_Option of oCustomerDD iField iOption To bOption

The Field_Option example shown above requires that the field number of the field you are querying is stored in the variable :iField. Retrieving this field number requires some special code. The Get statement version of this example can be rewritten in the following way:

Get Field_Option of oCustomerDD FIELD Customer.Name iOption To bOption

where Customer.Name is the field identifier of the field that you are querying. This is a much more convenient way to pass parameters to Function methods that require field numbers. Unfortunately the expression syntax version cannot pass field identifiers in this way. Refer to Declaring Procedure Set Methods for more information.


Naming Conventions

We recommend that you use Hungarian notation when naming identifiers in your Visual DataFlex applications. The Hungarian notation is a set of detailed guidelines for naming identifiers such as types and variables. The convention is widely used in many programming languages, especially in Windows programming.

The Goals of Notation

  • To identify global variables; One common programming problem is the misuse of global variables.

  • To identify class definitions; Naming conventions for classes serve two purposes: They explicitly identify an identifier as a class name, and they avoid naming clashes with objects and other variables or reserved words.

  • To identify constants; Named constants need to be identified so that you can tell whether you are assigning a variable a value from another variable or from a named constant.

  • To enhance readability

Base Types

Hungarian notation works by prefixing identifiers with standardized codes. Each prefix to categorize the identifier according to its type and use.

The first level of identifier prefixes identify the base types. The base type identifies the fundamental type of identifier that you are naming (e.g., is it a class, object, constant or variable). Hungarian notation prefixes each type of token to identify its base type as outlined in the following table:

Base Type Prefixes

Prefix | Meaning
c | Class
C_ | Constant
g | Global Variable
o | Object
p | Property
t | Struct Type
 | Local Variables (no-prefix)

All the prefixes, except C_ for constants, are in lowercase and placed before the rest of the token name.

Note: The Local variable base type does not use a prefix.

SubType

Subtypes go a step beyond the base type and describe the specific use of constants, global and local variables.

SubType Prefixes

Prefix | Meaning
b | Boolean
d | Date
h | Handle
i | Integer
n | Number
r | Real
s | String
v | Variant
ri | RowId
 | Struct type variables (no prefix)

All the prefixes are in lowercase and placed before the rest of the token name but after the base type prefix.

Note: Struct type variables do not use a prefix.

Arrays

The naming convention for array variables is to prefix the variable as per the array's declared type, then append 's' to indicate a plural.

Examples of Hungarian Notation

define C_PI for 3.14156 // A constant.

Integer giCount // A global Integer variable.

String gsName // A global String variable.

Struct tAddress // A Struct Type

Integer iStreetNumber // An Integer Struct Member

String sStreetName // A String Struct Member

String sCity

End_Struct

Class cSearchButton is a Button // A Class

Procedure Construct_Object

Forward Send Construct_Object

Property Integer piCount 0 // An integer property.

Property String psName "" // A string property.

End_Procedure

Procedure OnClick

Boolean bFound // A local Boolean variable.

Integer iCount // A local Integer variable.

String[10] sNames // A local string array

// Note, Struct Variables do not use notation

tAddress MyAddress

End_Procedure

End_Class

Object oSearchButton is a cSearchButton // An Object

End_Object


Language Guide

This help file is about Visual DataFlex's object oriented programming language. It presents a formal definition of the syntax and use of the Visual DataFlex language. You can also find detailed information about the syntax of specific commands in the Visual DataFlex Language Reference.

  • Tokens

  • Data Types, Variables and Constants

  • Expressions

  • Statements

  • Global Procedures and Functions

  • Classes

  • Objects

  • Programs

  • Sequential File Input and Output

  • Accessing Class Attributes with Expressions

  • Naming Conventions

What this help file contains

This manual presents several kinds of information:

  • The first ten chapters define the Visual DataFlex language, examining each element of a program in detail and how it fits into the structure of a program.

  • The remaining chapters present technical information on various specific programming operations such as file input and output, the Database API, and advanced function method calling.

Help File Conventions

Typefaces and Symbols used in this Manual

Special typefaces and symbols are used when describing the syntax of the Visual DataFlex programming language in this help file. These symbols are described in the following table.

Typeface or Symbol | Meaning
Monospace type | Monospaced text is used to represent programming code and code elements as it must be typed. It is also used for all the following symbols.
[] | Square brackets are used to enclose optional syntax. You should not type square brackets verbatim.
{identifier} | Text within {} braces represents Visual DataFlex identifiers such as variable names, object names, or method names.