CSIDE
CSIDE
ISBN 87-7849-063-4
Doc. no. W1-XA-BAF1-CB08-O
It is assumed that you are comfortable with using Microsoft Windows and the
user interface of Microsoft Windows applications. If not, you should consult
the Microsoft Windows documentation.
The manual is divided into parts. When you create a C/SIDE application you
combine five types of application objects into a whole that solves a business
problem. Each of the five types of application objects has its own part in the
manual. The order in which the parts appear corresponds to the order in which
you are most likely to need them when you design a new application. That is,
the part dealing with tables comes before the part dealing with forms, as you
should design your tables before you design forms.
Each part contains one or more chapters. The first chapter in a part always
deals with the fundamentals, for example ‘Form Fundamentals’, and the
succeeding chapters present more advanced information.
Each chapter starts with a short introduction and an overview of the chapter
contents. This makes it possible for you to quickly decide whether this chapter
is the place to find the information you need.
Your software package comes with three printed manuals for the accounting
and business management part of the program:
Navision Financials Installation and System Setup. This manual explains the
more technical aspects of Navision Financials. You will find information about
user administration, backup procedures and other items that are also relevant
for application developers.
Table of Contents
Part 1 Fundamentals
Chapter 1 C/SIDE Fundamentals 3
The C/SIDE User Interface 4
What Is a C/SIDE Application? 8
The Physical and the Logical Database 11
Part 2 Tables
Chapter 3 Table Fundamentals 23
What Is a Table? 24
What Are Keys? 30
Saving, Viewing, and Sorting Data 37
Dividing the Database into Companies 40
Special Table Fields 42
Part 3 Forms
Chapter 6 Form Fundamentals 89
What Are Forms? 90
i
Contents
Creating Forms 93
Selecting, Moving and Adjusting Controls 100
Saving, Compiling and Running Forms 106
Part 4 Reports
Chapter 9 Report Fundamentals 165
What Are Reports? 166
What Happens When a Report Runs? 171
The Report Designer 175
Saving, Compiling and Running Reports 179
ii
Contents
Part 5 Codeunits
Chapter 12 Codeunit Fundamentals 235
What Is a C/SIDE Codeunit? 236
Creating Codeunits 238
Using Codeunits 246
Part 6 Appendixes
Appendix A C/SIDE Specifications 323
Specifications for the DBMS 324
Specifications for C/SIDE Application Objects 325
iii
Contents
iv
Part 1
Fundamentals
Chapter 1
C/SIDE Fundamentals
(A) Depending on the task you are working on, the system automatically changes the
icons.
(B) This is also where the user interacts with your applications.
Tables are the Tables are the fundamental objects that store the actual data; you need other
fundamental objects application objects to insert, modify, delete or show data from tables. You will
that store the actual data
typically will use a form to enter or retrieve data from the database and use a
report to print data.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
all application objects are identified by an ID number. There are, however,
restrictions about which numbers you should use when you create your own
application objects. Please contact your NTR for more information.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The main tool used for developing applications in C/SIDE is the Object
Designer (choose Tools, Object Designer). This is the tool you use to view and
design tables, forms, reports, dataports and codeunits.
In the Object Designer you choose the type of application object you want to
work on. From the Object Designer you can run an application object or start an
application object designer to modify the design of an existing application
object or create a new application object. The following picture shows how to
use the Object Designer in more detail.
The Object Designers The table below lists the tools you can access via the Object Designer and
when you should use them.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
there is no access to the DataPort Designer in this version.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The table below summarizes the information in the figure, and explains what
each type of application object is used for.
C/AL C/AL is the language used for writing functions in C/SIDE. In the table
above, ‘C/AL’ refers to functions written in this language.
Triggers When specific things happen to the application objects, the system
automatically activates a trigger. Inside a trigger you can add your own C/AL
code if you want to modify the default behavior of the application object or
extend its functionality.
Keys A key defines the order in which data are stored in your tables. You can
speed up searches in your tables by defining several keys which sort your
information in different ways.
Controls Controls are objects on a form or report that display data, perform
actions or decorate the form. Typical examples are command buttons and text
labels.
Request Form A request form is a form that is used in a report. Before a report
is run, a request form appears to let the user specify filters and options for the
report.
Data Items A data item is a building block you use for defining a model of
your data when you create a report. By using a hierarchy of data items you
define which data your report should include. A data item represents a table,
and when you run a report, the system cycles through the records in the
associated table. A data item can have one or more sections.
As a typical database user, you are not concerned with where each piece of
data is stored on the hard disk or what its size is; you just want to be sure that
when you refer to a name, for example, the correct value is returned. This is
why the C/SIDE database system provides you with a conceptual
representation of data that does not include too many details of how the data
is stored. An abstract data model is used for this conceptual representation.
This data model uses logical concepts (such as objects, their properties and
their relations) which are easier to understand.
This leads us to distinguish between the logical and the physical database.
When we speak about the logical database we are concerned only with the
structure of the data and the relationships between different bits of
information. That is, we do not deal with how these structures and relations
are implemented. When we speak about the physical database we deal only
with how the structures in the logical database and the search paths between
them are implemented.
In this book the term database should be interpreted to mean the logical
database unless otherwise noted.
What the user sees as a coherent set of information in the C/SIDE database
system can be stored in several physical disk files, but this is transparent to
the user. The figure below illustrates how one logical database can be
physically stored on three hard disks but still comprise a single (logical)
database.
Fields A field is the smallest logical structure used in the C/SIDE database. A
field is used to hold a single bit of information, such as a name, ‘Joe,’ or an
amount, ‘2,352.00.’ Any particular field can hold information of only one
specific type. (The C/SIDE database system distinguishes between 10 different
types of information.) Fields are assembled into a structure called a record. On
its own, a field is not very useful, as it can hold only a limited amount of
information. By assembling these small bits of information into records we get
a much more flexible ‘information-holder’ that is also better organized because
it keeps together fields that belong together.
Database
Companies are the
largest logical Company
structures in a C/SIDE Table
database Record
Field
Fields are the
smallest logical
structures in a C/SIDE
database
Designing the Tables Begin by designing a data model that you use to
determine how the data will be stored and how it can be most meaningfully
utilized. The data model determines:
· designing forms (to enter and retrieve data) and reports (to retrieve and
present data).
· creating C/AL code to connect the application objects.
The above steps depend on each other. When you go from one step to another
you will often have to rethink some of the decisions you made in the previous
step.
Your interviews of the end users will give you a good knowledge of which
questions the end users want answered and thus of the information that forms
and reports should provide. This does not necessarily tell you how you should
structure your tables, however.
Basically, an ER model divides all the elements of a real world situation into
two categories: entities and relations. An entity is a ‘thing’ in the real world
with an independent existence. An entity may be an object with a physical
existence, such as a particular car or person, or it may be an object with a
conceptual existence, such as a company or a job. Relationships describe how
the entities are related.
1 Identify the types of entities associated with your problem. Create tables to
represent each of these types of entities.
2 Identify the properties of each entity type and create fields in the tables to
represent each of these properties.
3 Identify the relationships between the entities and add these relationships
to the tables.
The table below summarizes how basic ER model concepts relate to C/SIDE
concepts.
Example
Suppose that your analysis using the ER model has revealed that you have an entity type describing
your company’s customers. This has led you to define a Customer table:
Customer Table Company Name Contact Person Phone ... Payment Method
...
...
...
...
...
Your analysis has shown that you need fields such as Company Name, Contact Person, Phone and
Payment Method. When you implement the Customer table, you select the following field types:
In order for a field to be a key for a table, the uniqueness constraint above
must hold for every record in the table. This constraint prevents any two
records from having the same value for the key field. It is not a constraint on a
specific record but a constraint for all records in the table, considered together.
Sometimes you will be able to define several keys for a table. Refer to the
section How to Define a Primary Key on page 30, which discusses the concepts
of keys.
texts on relational database design can teach you how to obtain these normal
forms. Some good starting points are the books mentioned on the last page of
this chapter.
Creating Forms Forms are used to present or collect information. You have
access to a number of design elements, such as text, data, pictures, lines, and
color.
Creating C/AL Codeunits Codeunits are containers for storing C/AL code.
When you put the code into a codeunit, you can reuse the same algorithms
many places in your application. This reduces the size of the application and
makes it easier to maintain.
Testing and Refining the Application Before you release your application,
you have to analyze your design for errors. This is normally an iterative
process.
At this point you will have a useful application. If you took the time to plan all
steps of the application design carefully, you also have an application that is
fully documented. This will prove to be a great help when you need to make
future adjustments and additions.
· What Is a Table?
· What Are Keys?
· Saving, Viewing, and Sorting Data
· Dividing the Database into Companies
· Special Table Fields
Chapter 3. Table Fundamentals
Columns: Fields
Rows: Records
A table consists of two parts: the table data and a table description. The table
data is the part users often think of as comprising the database, because it
contains the actual records with their data fields. The layout and properties of
those fields, however, are specified by the table description. The table
description is not directly visible to the user. The next figure illustrates how the
table data and the table description together form a table.
Table Data
24 What Is a Table?
Chapter 3. Table Fundamentals
field. When you design a new table, you also specify which keys you want the
system to maintain. All these characteristics are stored in the table description
when you save your table design.
Table description
Table Properties
Triggers
Fields Properties
Triggers
Keys Properties
The table description contains some properties that are related to the table,
while others are related the fields in the table. Still other properties are related
to the keys in the table. You can also see from the figure that triggers are
defined both for the table and for the fields in the table.
Don’t worry if you are not familiar with these terms already. You’ll learn more
about them on the following pages, and the next chapter, Customizing and
Maintaining Tables on page 51, provides a more detailed description of how to
customize your tables by modifying the properties and creating triggers.
Creating a Table
When you first create a table, it will not contain any data. When you create the
table you have to decide which types of information you want to store in it.
What Is a Table? 25
Chapter 3. Table Fundamentals
To create a table:
1 From the View menu, choose Object Designer. C/SIDE will display:
3 Click the New button. C/SIDE will show the Table Designer
In the Table Designer, for each field you add to the table, you enter the field
number, name, data type and, optionally, a length and a description. The
following subsections describe how to do this.
26 What Is a Table?
Chapter 3. Table Fundamentals
After you have added fields to a table in the Table Designer, you must save the
table before you can add any records. Once you have saved a table, it will
appear in the list of tables in the Object Designer.
All the tables and fields you create have two forms of identification:
What Is a Table? 27
Chapter 3. Table Fundamentals
28 What Is a Table?
Chapter 3. Table Fundamentals
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
data is stored with a four byte alignment because of performance
considerations. The sizes of text, code, and binary fields (that can have
variable lengths) are rounded up to the nearest value that is a multiple of four.
This means that, for example, a text string of 10 characters will occupy 12
bytes.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Besides the ordinary fields discussed in this section, the C/SIDE database
system also includes two special types of fields
· FlowFields
· FlowFilters
How these special fields provide powerful data retrieval mechanisms is
described in the section Special Table Fields on page 43.
What Is a Table? 29
Chapter 3. Table Fundamentals
The primary key is always active; the DBMS keeps the table sorted in primary
key order and rejects records with duplicate values in primary key fields.
Therefore, the values in the primary key must always be unique. Be aware that
it is not the value in each field in the primary key that must be unique, but
rather the combination of values in all the fields comprising the primary key.
When you create a table in the table designer, C/SIDE automatically uses the
field with the lowest field number as primary key.
2 Choose Keys from the View menu to define a primary key. C/SIDE will
display the following:
3 On the first line in the Key window, enter the primary key as a comma-
separated list (for example: ID Number,Name).
2 Choose Keys from the View menu to define secondary keys. C/SIDE will
display the following:
Enter the
secondary keys
here as comma-
separated lists.
3 The first line shows the primary key. Enter the secondary keys on the
following lines as comma-separated lists (for example: Name,Address).
A secondary key uses an additional data structure called an index. The idea
behind an index is similar to the idea behind the indexes used in common
textbooks. A textbook index lists important terms at the end of the book in
alphabetical order. Next to each term is a list of page numbers where the term
appears. We can search the index to find a list of page numbers (addresses)
and easily locate the term in the textbook by searching the specified pages.
Hence, the index is an exact indicator of where each term occurs in the
textbook.
When you define a secondary key and mark it as active, the system will
automatically maintain an index reflecting the sorting order defined by the key.
Several secondary keys may be active at the same time.
A secondary key can be changed into an inactive key. This means that the
DBMS does not use time during updates to maintain its index. Furthermore, an
inactive key doesn’t occupy database space.
Inactive keys can be reactivated; this process may consume some time,
depending on the size of the table, because the DBMS has to scan the entire
table to rebuild the index.
The fields comprising the secondary keys are not guaranteed to contain unique
data; the DBMS does not reject records with duplicate data in secondary key
fields. If two or more records contain identical information in the secondary
key, the DBMS will use the primary key for the table to solve this conflict. The
example below shows how the primary key influences the sorting order when a
secondary key has been activated:
We assume that the Customer table includes four entries (records). The
records in the Customer table have two fields: Customer No. and Customer
Name. The Key List for the Customer table is:
If you select the secondary key for sorting, the ordering will be based on the
contents of the Customer Name field. As the contents of these fields are not
unique, the records will have to be subsorted according to the primary key.
In this case the last two records, which have the same Customer Name, have
been ordered by Customer No.
increase the number of when you retrieve data in when you enter data because
secondary keys marked as several different sorting C/SIDE has to maintain the
active sequences because the indexes for each secondary
system has already sorted key.
the data.
decide to use only a few keys when you enter data because when you retrieve data. You
C/SIDE has a minimal may have to define or
number of indexes to reactivate the secondary
maintain. keys to get the appropriate
sortings. Depending on the
size of the database, this can
take some time, as the
system builds the index.
The decision whether to use a few or many keys is not easy to discuss in
general. The choice of appropriate keys and the number of active keys to use
should be the best compromise between maximizing the speed of data
retrieval and maximizing the speed of data updates (operations that insert,
delete or modify data). In general it may be worthwhile to deactivate complex
keys if they are used only rarely.
The overall speed of C/SIDE will depend strongly upon a number of factors:
The figure illustrates the first four keys of this table–the primary key and three
secondary keys. The primary key consists of a single field ID. The first
secondary key contains two field IDs, while the second and third secondary
keys contain three and four fields respectively.
2 From the File menu, choose Save. C/SIDE will display the following:
Normally you use a form to view the data in a table, but you can also view the
data in a table directly by running the table from the Object Designer.
2 In the Object Designer, select the table you want to view and click the Run
button. C/SIDE will display the data in a tabular format:
The order in which the information appears in the window above reflects the
sorting order defined by the current key. If more than one key is defined for the
table, you can switch between the sorting orders these keys define.
1 Run the table from the Object Designer. C/SIDE will display the following:
Select ascending or
descending order
here
3 Select the key that defines the sorting order you want, and choose whether
you want the records displayed in ascending or descending order. Click the
OK or the Apply button to apply the new key. If you choose OK, the Sort
dialog closes; if you choose Apply, the Sort dialog stays open. Using Apply
is convenient if you frequently change the sorting order.
2 In the Object Designer, select the table you want to add records to and click
the Run button. C/SIDE will display the table in a tabular format:
3 Place the cursor in a blank line. Enter data in the fields and press Enter. You
can use Tab, Shift-Tab and the arrow keys on the keyboard to navigate
between the fields.
4 When you have finished entering data, close the table. (You do not have to
save it, as the records are saved and updated whenever you leave a field
after entering a value in it.)
The four table descriptions on the left apply to each of the data tables, which
are logically sorted into three companies. The records in the tables G/L
Account, Customer and Vendor, all have the same structure and the same field
definitions, even though they belong logically in three different companies.
Only the data stored in the fields differ. As the information in a Table
Description can be used by tables from more than one company, no redundant
information will be stored. This minimizes the size of the database.
Even though you have selected a specific company, you can still access data in
any table in any other company. To do so, you must use the C/AL function
<Record>.CHANGECOMPANY to explicitly define which other company you
want to access.
More than one application can access the same company and the same
table(s) at the same time. How the DBMS controls these multiple accesses is
described in the section What is Table Locking? on page 350.
· FlowFields
· FlowFilter fields
FlowFields are not a permanent part of the table data. A FlowField can be
thought of as a virtual field, which is an extension to the table data. Actually
the information in the FlowFields exists only at run time. The values in
FlowFields are automatically initialized to 0 (zero). To update a FlowField, you
must use the C/AL function <Record>.CALCFIELDS. Notice that if a FlowField is
the direct source expression of a control on a form, the FlowField will
automatically be calculated when the form is displayed.
Consider the Customer table in the figure below. This table contains two FlowFields. The field named
Any Entries is a FlowField of the Exist type, and the Balance field is a FlowField of the Sum type.
Virtual part of
Customer Entry (Table data) the table data
10000 10
10000 20
10000 30
10010 40
10010 50
10020 60
10020 70
10020 80
10040 90
10040 100
10040 110
The figure shows that the value in the FlowField Balance for customer number 10000 (Windy City
Solutions), is retrieved from the Amount column in the Customer Entry table. The value is the sum of
the amount fields for the entries that have the customer number 10000, that is
Sum = 10 + 20 + 30 = 60.
The values shown in the Balance column in the Customer table for customers number 10010, 10020,
10040 are found in the same way. Customer number 10030 has the value 0 (zero), as there are no
entries in the Customer Entry table that have a Customer No. that equals 10030.
In this example the Balance FlowField in the Customer table reflects the sum of a specific subset of
the Amount fields in the Customer Entry table. How the calculation of a FlowField is to be made, is
defined in a calculation formula. Sums in FlowFields are always based on a calculation formula. The
calculation formula for the Balance field is
Correspondingly, the Any Entries field, which indicates whether any entries exist, has the following
definition:
To create a FlowField:
1 Design the table in the Table Designer. C/SIDE will typically display:
2 Click on the line defining the field that you want to turn into a FlowField.
3 From the View menu choose Properties. C/SIDE will display the property
sheet:
5 Now you have to enter a calculation formula for the FlowField. This is done
with the CalcFormula property. The next section tells you how.
<CalculationFormula> ::=
Exist(<TableNo> [WHERE (<TableFilters>)]) |
Count(<TableNo> [WHERE (<TableFilters>)]) |
[-]Sum(<TableNo>.<FieldNo> [WHERE (<TableFilters>)]) |
[-]Average(<TableNo>.<FieldNo> [WHERE (<TableFilters>)]) |
Min(<TableNo>.<FieldNo> [WHERE (<TableFilters>)]) |
Max(<TableNo>.<FieldNo> [WHERE (<TableFilters>)]) |
Lookup(<TableNo>.<FieldNo> [WHERE (<TableFilters>)])
<TableFilters> ::=
[<TableFilter> {,<TableFilter>}]
<TableFilter> ::=
<DstFieldNo>=CONST(<FieldConst>) |
<DstFieldNo>=FILTER(<Filter>) |
<DstFieldNo>=FIELD(<SrcFieldNo>) |
<DstFieldNo>=FIELD(UPPERLIMIT(<SrcFieldNo>)) |
<DstFieldNo>=FIELD(FILTER(<SrcFieldNo>)) |
<DstFieldNo>=FIELD(UPPERLIMIT(FILTER(<SrcFieldNo>)))
where...
Symbol Explanation
<TableNo> Specifies the table holding the information to be used in the FlowField.
<FieldNo> Specifies the column from which you want to compute values.
<TableFilters> A list of filters to be used in the computation of the FlowField.
<TableFilter> A table filter can be one of the following: a constant expression, a filter
expression, a value from ordinary fields or a FlowFilter field (FlowFilter fields are
discussed in the next section). Notice that it is a requirement that a key for the
other table exists and includes the fields used in the filters.
<DstFieldNo> Specifies the destination field number.
<SrcFieldNo> Specifies the source field number.
<Filter> A filter expression such as 10|20..30.
To create, view, or edit a calculation formula:
1 Click the field for which you want to create, view, or edit the calculation
formula.
2 From the View menu, choose Properties. Find the CalcFormula property in
the property sheet:
Enter the
calculation formula
here
3 You can either enter the calculation formula directly or click the assist-edit
button. When you click the assist-edit button, C/SIDE will display:
5 At each line in this window, you can define a field filter. For each field filter
you must specify a field, a type, and a value and you can set the
OnlyMaxLimit and the ValueIsFilter options. The following example below
illustrates where the information in this window come from.
Example
The Balance at Date field in the G/L Account table is a decimal type FlowField. This field is calculated
from values in the Amount column in the G/L Entry table.
This means that if the user enters a date filter expression in the Date Filter field, it will be transferred
via the table filter and used in the Date column in the G/L Entry table.
You can use the OnlyMaxLimit option to remove the lower bound from a range defined by a filter
expression. For example, if the filter expression is defined as a range x..y, setting the OnlyMaxLimit
option will transform the expression into ..y.
The ValueIsFilter option determines how the system interprets the contents of the field referred to in
the Value column in the table filter window. For example, if the field contains the value 1000..2000,
setting the ValueIsFilter option will cause this value to be interpreted as a filter rather than a specific
value.
Table B
Table A Constants
Ordinary fields
Table C
Calculation
FlowFilter fields
Formula
FlowFields
Table D
The above figure illustrates the relations between various types of database
fields and the calculation formula. The filters defined in the calculation formula
can consist of constants, of values from ordinary fields and of filters given as
parameters in FlowFilter fields. FlowFilter fields are fields in which the end user
can enter input a filter (via the user interface in a C/SIDE application) that will
affect the calculation of a FlowField.
· Table Properties
· Field Properties
· Key Properties
2 Click the Table button in the Object Designer window to get a list of the
tables.
3 Select a table and click the Design button. C/SIDE will display the table in
the Table Designer:
The Table
Designer
4 Place the cursor on an empty line in the Table Designer. (If you place the
cursor on a line defining one of the fields in the table, you will get the
properties for the field instead of those for the table.)
5 Choose Properties from the View menu. C/SIDE will display the Property
Sheet:
The Property
Sheet displays the
properties for the
current table.
6 If you want to modify the setting of a property, simply enter the new value
on the Property Sheet. When you have entered the new value, update the
property by either pressing Enter or simply moving the cursor away from the
field.
7 To get Help for a property, point at it on the Property Sheet and press F1.
Example
LookUpFormID is a typical example of a property you will want to modify. The default value for the
LookUpFormID property is <Undefined>. By changing this value, you can determine which form the
system will display when F6 (Lookup) is pressed.
Refer to the online C/SIDE Reference Guide for additional information about
the above properties.
2 Click the Table button in the Object Designer window to get a list of the
tables.
3 Select a table and click the Design button. C/SIDE will display the table in
the Table Designer:
4 Place the cursor on the line in the Table Designer that defines the field for
which you want to access the properties.
5 From the View menu, choose Properties. C/SIDE will display the Property
Sheet:
6 If you want to modify the setting of a property, simply enter the new value
on the Property Sheet. When you have entered the new value, update the
property by either pressing Enter or simply moving the cursor away from the
field.
7 To get Help for a property, point at it on the Property Sheet and press F1.
Example
The DecimalPlaces property is a typical example of a field property you may want to change. When
you create a new field of the type decimal, C/SIDE will assume that you want the value to be
formatted as a currency. If your decimal field will not contain a currency, you can use this property to
determine the number of decimal places that will appear on the screen.
Refer to the online C/AL Reference Guide for additional information about the
above properties.
2 Click the Table button in the Object Designer to get a list of the tables.
3 Select a table and click the Design button. C/SIDE will display the table in
the Table Designer:
5 Place the cursor on the line defining the key for which you want to view or
modify the properties.
6 From the View menu, choose Properties. C/SIDE will display the Property
Sheet:
The Property
Sheet for a key
7 If you want to modify the setting of a property, simply enter the new value in
the Property Sheet. When you have entered the new value, update the
property by either pressing Enter or simply moving the cursor away from the
field.
8 To get Help for a property, point at it on the Property Sheet and press F1.
· Table triggers
· Field triggers
Tables in C/SIDE have the following triggers:
1 Click the Table button in the Object Designer window to get a list of the
tables.
2 Select the table and click the Design button. The system will open the Table
Designer, containing a list of the fields in the table.
3 Choose View, C/AL Code (F9). C/SIDE will display the code for the table in
the Table Designer. The system uses the position of the cursor in the Table
Designer to determine what code to display. That is, if you place the cursor
on a specific field in the Table Designer, the code in the C/AL Editor is
automatically scrolled so that the first trigger related to that field appears at
the top of the window. If the cursor is placed on an empty line in the Table
Designer, the system shows the first trigger related to the table itself.
Notice, however, that the position of the cursor in the Table Designer does
not restrict your access to other triggers. You can always scroll up and down
through the triggers in the C/AL editor.
· One-to-Many Relationships
· Many-to-Many Relationships
· One-to-One Relationships
Because the one-to-many relationship is the most commonly used, this section
will focus on this type of relationship. If your database design model indicates
that you need to set up a many-to-many relationship, you probably have a
problem in your design–it may be inefficient. You normally break down a
many-to-many relationship into two one-to-many relationships. A one-to-one
relationship is usually undesirable and can often be avoided by simply
combining the two tables. To learn more about database design, refer to one of
the text books mentioned in the subsection Recommended Books on Database
Design on page 19.
<TableRelation> ::=
<TableNo>[.<FieldNo>] [WHERE ( <TableFilters> )] |
IF ( <Conditions> ) <TableNo>[.<FieldNo>]
[WHERE( <TableFilters> )] ELSE <TableRelation>
<Conditions> ::=
<TableFilters>
<TableFilters>::=
[<TableFilter> {,<TableFilter>}]
<TableFilter>::=
<DstFieldNo>=CONST(<FieldConst>) |
<DstFieldNo>=FILTER(<Filter>)
where...
Symbol Explanation
<TableNo> Specifies the related table.
<FieldNo> Specifies a field in the related table.
<Conditions> Table relations can be conditional.
<TableFilters> A list of table filters.
<TableFilter> A table filter can be either a constant expression or a filter expression.
<DstFieldNo> Specifies the destination field number.
<Filter> A filter expression such as 10|20..30.
<TableNo>[.<FieldNo>]
directly on the Property Sheet, whereas you will use assist-edit to enter the
more advanced table relations that use conditions and filters. Below you will
see how to create (basic) table relations by entering them directly on the
Property Sheet. In the following section, you will see how to use the assist edit
tool to do the same.
2 Click the Table button in the Object Designer window to get a list of the
tables.
3 Select a table for which you want to create a relationship, and click the
Design button. C/SIDE will display the table in the Table Designer.
4 Make sure that the cursor is placed in the field for which you want to set up
a relation. Select Properties from the View menu. C/SIDE will display the
Property Sheet for the field:
5 Enter the table relation directly in the value field for the TableRelation
property. Simple table relations use the syntax: <TableNo>.[<FieldNo>].
Refer to the next section to learn how to use the assist-edit tool to create
advanced table relations.
Example
Assume that you have an Orders table that stores orders and a Sales Person table that stores the
names of all sales person in your company. In the Orders table, you can include a field called Sales
Person that identifies the sales person. By setting up a relationship between these two tables you
can get the system to check whether the sales person field in the Orders table contains a valid code.
Example
Assume that you have a Vendors table of all your vendors and a Currency Code table. Then you can
create a relationship between a Currency Code field in the Vendors table and the Currency Code
table. This will allow users to lookup (F6) information about valid currency codes.
Example
Assume that you have a Vendors table and a Currency Code table as in the example above. If you
change one of the currency codes in the Currency Code table, the system will automatically
propagate this change to all the tables that refer to this code.
1 Start exactly as if you are creating a basic table relation. Repeat steps 1 to 4
as described on page 62.
2 Click the assist-edit button to the right of the Field field for the
TableRelation property. C/SIDE will display
3 Fill in Condition fields by using the assist-edit to set the relevant table
filters. For example, you can look up in different tables, based on the value
in an option field.
4 Enter in the Table field the name of the table to which you want to make a
relation, or use the lookup button to select a table from a list. In the Field
field, you can enter the name of the field or use the lookup button to select
from a list of fields (those in the table you have entered in the Table field).
5 If necessary, define a table filter (for the table in the Table field) in the Table
Filter field.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
C/SIDE is designed to ensure that you never lose data when you modify the
design of a table that contains data.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Modification Rules
Changing a field name You can always change the name of a field.
Changing a data type You can change the data type for a field only if there is no data
in this field for any of the records in the table. There is one
exception to this rule: you can change the data type of a field
from Code to Text even if the field contains data for some
records.
Deleting a field In order to delete a field, you must delete all data from the field
in all records in the table. Furthermore you must remove all
references to the field from other tables, forms and reports
Changing the length of a You can always increase the length of a String field. Whether
String field you can decrease the length of a String field depends on the
contents of all the values in the column in the table. The
minimum length of a String field is determined by the longest
string in the column.
You can do almost anything with a temporary table that you can do with a
normal database table; the only differences between a normal database table
and a temporary table are that:
· Temporary tables aren’t stored in the database but only held in memory on
your workstation until the table is closed.
· Temporary tables can’t have more than one key
· The write transaction principle that applies to normal database tables does
not apply to temporary tables. If you are not familiar with the transaction
principle, please refer to the section Write Transactions and Recovery on
page 344
C/SIDE
Server
When you need to perform many operations on data in a specific table in the
database, you can load the information into a temporary table while you
modify it. Because all operations are local, this will speed up the process.
1 We assume that you are working in the C/AL editor. From the View menu,
choose C/AL Globals or C/AL Locals, depending on whether your variable is
going to be global or local. If you choose C/AL Globals, C/SIDE will display:
2 Enter a name for the temporary table variable and enter Record as data
type. Use the lookup button in the Subtype field to select the table you want
to make a temporary copy of.
3 With the cursor still on the line that defines the temporary table, choose
Properties from the View menu to display the Property Sheet. C/SIDE will
display:
After you have created a temporary table as described above, you can use it in
your C/AL code. You can apply filters and perform searches just the way you do
when you work with normal database tables.
It is possible to read, write, modify and delete the information in system tables.
· User Table
· Member Of Table
· User Group Table
· Permission Table
· Company Table
· Database Key Groups Table
The first four tables in the list above all deal with user permissions.
About permissions
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
In order to insert, modify or delete information in the User, Member Of, Group,
and Permission tables, you must have at least the same permissions as the
users you want to modify. This means that you can’t assign to other users or
take away from them permissions that you don’t have yourself.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
For each ID defined in your database, the User system table includes
information about the password (displayed encrypted on your screen), the real
name of the user and for how long this ID will be valid. You can create new user
IDs by entering appropriate data in this table; correspondingly, you can
remove a user ID by deleting the record from this table. (Of course this depend
on your own permissions.)
Deleting a record
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
If you delete a record in the User system table, the system will automatically
remove the corresponding entries in the Member Of system table.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Deleting a Record
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
If you delete a record in the User Group system table, the system will
automatically delete the corresponding entries in the Member Of and
Permission system tables.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
· Read
· Create/Insert
· Modify
· Delete
· Execute
Category Includes...
Simple Date, Integer, File, Drive
Advanced Monitor, Session, Database File, Table Info
The simple virtual tables contain lists of simple information, while the
advanced virtual tables contain information that is more complex.
You can apply filters to the Period Type, Period Start, and Period End fields to
easily get a subset or range of days, weeks, months, quarters or years to use in
your forms or reports.
Example
The Date virtual table is most frequently used to provide a range of dates, the G/L Balance form
below is a typical example. You will learn how to design forms in part 3, Forms, on page 87.
This information is
provided by the
Date virtual table
This virtual table is used by C/AL programmers to get an overview of the time
consumption of specific operations. C/AL programmers can use the
information in this virtual table to tune their code for optimal performance. The
Monitor virtual table has the following fields:
You can access the Monitor virtual table directly from C/SIDE’s user interface:
1 From the Tools menu, choose Client Monitor. C/SIDE will display:
3 You can now close the Client Monitor window while you perform the tasks
you want to investigate.
4 When you have completed these tasks, choose Client Monitor from the
Tools menu to display the window again.
3 Click the drill-down button in the Current Sessions field. C/SIDE will display:
3 Click the drill-down button in the Database Name field. C/SIDE will display:
Note that the above window displays only some of the fields in the Database
File virtual table.
Note that this window doesn’t display all the columns in the Table Information
virtual table. Use the horizontal scroll bar to view the information in the hidden
columns.
Forms can be used to access one table at a time, or they can combine
information from a number of different tables. A form can display information
that is calculated on the fly, as the form is displayed, and it can contain
information (such as a label) that is not related to any table, or purely
decorative elements (such as bitmap pictures).
The figure below shows the components of a form and how they are related.
This and the following chapters will explore each component in depth.
Form Description
Form Properties
Triggers
Controls Properties
Triggers
Forms are created and edited in the Form Designer.
Some controls are called container controls. An example is a frame. The frame
itself does not display data or information, but it can contain a number of other
controls that you want to group. A powerful container control is the tab control.
A tab control really is a number of frames or pages that are placed on top of
each other. The user can switch between the pages by clicking the tabs that
have captions. Tab controls make it possible to group information on a form so
that each page is not cluttered with information, and it is very fast and easy to
switch between pages.
The controls on a form that is bound to a table are usually bound to fields in
the same table. There need not be a control for every field in the table, nor do
all controls on the form need to be bound to table fields: controls that aren’t
bound to fields are called unbound controls. An example is a command button
that causes the information on the form to be printed; another is a control that
contains a descriptive text. An important category of unbound controls
includes controls displaying information–based on the underlying table or
user-entered values–that is calculated as the form is displayed.
The form itself also has properties. For example, you can specify whether the
form is to be used only for displaying information or whether it will be possible
to insert new records or update existing ones.
Properties are defined on the Property Sheet that can be edited when the form
is opened in the Form Designer.
Forms that display one record at a time are called card forms, while forms that
show several records at a time are called tabular forms. The form wizard will
help you create either type.
A Card Form
A Tabular Form
Creating Forms 93
Chapter 6. Form Fundamentals
4 If you are creating a form that is related to a table, type the name of the
table in the Table field. You can also click the Lookup button and choose the
table from a list. When the table name has been entered in the field, press
Enter.
5 Select the option called “Create a form using the wizard.” Then you must
select the type of form you want the wizard to create: card form or tabular
form. After this, click OK.
1 The system asks whether you want the form to have tabs or whether it
should be a plain form. A form with tabs is a multi-page form where the user
can switch between pages by clicking on the tabs.
94 Creating Forms
Chapter 6. Form Fundamentals
2 If you choose to create tabs, type a caption for each tab you want. In either
case, click the Next button when you are ready to continue.
3 In the next form the wizard displays, you must choose which fields from the
database table you want on your form. If you are creating a form with tab
controls, begin by choosing the page on which you want certain fields to
appear. You can switch between pages by clicking on the tabs, which have
the captions you have defined.
4 The form the wizard displays contains two lists: one with Available Fields
(all the fields in the table), and one with the fields that have been selected,
the Field Order list. To insert a field in the Field Order list, select it in the
Available Fields list and click the > button. You can insert all the fields at
Creating Forms 95
Chapter 6. Form Fundamentals
once by clicking the >> button. (You can remove fields from the Field Order
list by selecting them there and clicking <; you can remove them all at once
by clicking <<.)
5 The order of the fields in the Field Order list is the order in which the fields
will appear on the form. If you want a different order, move a field by
removing it from the Field Order list and inserting it again in the position you
want.
6 By clicking the Separator button, you can insert a small amount of extra
vertical space between the controls; this allows you to group information
together in a logical and visually pleasing way. (Note that tabs provide a
more powerful way of grouping information together on a form.) The
separator will be inserted after the field that is currently selected on the
Field Order list; you can remove a separator by selecting it and clicking the
< button.
7 To insert a column break, click the Column Break button. The rules for
insertion and deletion are the same as for a Separator. If you feel you need
to create three or more columns, you should consider using tabs instead.
8 When you are satisfied, click the Finish button. The form wizard will create
your form, and the Form Designer window will open containing the new
form. You can test-run the form by choosing Run from the File menu.
9 Close this window and answer Yes to save the form. You will be prompted to
enter an ID number and Name for the new form, and you can choose
whether or not the form will be compiled now. (You can also do it later, by
selecting the form in the Object Designer window and choosing Compile
from the Tools menu.)
10 When the form has been saved and compiled, it can be run. Select the form
from the Object Designer window and click the Run button.
96 Creating Forms
Chapter 6. Form Fundamentals
1 You are prompted to choose the fields from the database table that you
want on your form. The form the wizard displays contains two lists: one with
Available Fields (all the fields in the table), and one with the fields that have
been selected, the Field Order list. To insert a field in the Field Order list,
select it in the Available Fields list and click the > button. You can insert all
the fields at once by clicking the >> button. (You can remove fields from the
Field Order list by selecting them there and clicking <; you can remove them
all at once by clicking <<.)
2 The order of the fields in the Field Order list is the order in which the fields
will appear on the form. If you want a different order, move a field by
removing it from the Field Order list and inserting it again in the position you
want.
3 When you are satisfied, click the Finish button. The form wizard will create
your form, and the Form Designer window will open containing the new
form. You can test-run the form by choosing Run from the File menu.
4 Close this window and answer Yes to save the form. You will be prompted to
enter an ID number and Name for the new form, and you can choose
whether or not the form will be compiled now. (You can also do it later, by
selecting the form in the Object Designer window and choosing Compile
from the Tools menu.)
Creating Forms 97
Chapter 6. Form Fundamentals
5 When the form has been saved and compiled, it can be run. Select the form
from the Object Designer window and click the Run button.
4 If you are creating a form that is related to a table, type the name of the
table in the Table field. You can also click the Lookup button and choose the
table from a list.
98 Creating Forms
Chapter 6. Form Fundamentals
7 You can test-run the form at any point by choosing Run from the File menu.
8 When you have finished designing the form, close the Form Designer
window, and answer Yes to save the form. You will be prompted to enter an
ID number and Name for the new form, and you can choose whether or not
the form will be compiled now. (You can also do it later, by selecting the
form in the Object Designer window and choosing Compile from the Tools
menu.)
Creating Forms 99
Chapter 6. Form Fundamentals
Selecting Controls
To move or adjust a control, you must first select it. As some operations can be
applied to only one control at a time and others to a group of controls, controls
can be selected both individually and as groups.
You select a control by pointing at it and clicking the mouse. In order to make it
easier to see what the mouse cursor is pointing at, the appearance of the
cursor changes as it is moved around the design area. The default appearance
is a cross, which means that the cursor is not currently pointing at any control.
As soon as the cursor points at a control, it changes into a selection cursor.
Clicking the mouse selects the control, which will be surrounded by a box with
sizing handles.
Multiple Selections
A multiple selection is a group of controls that are all selected. You make a
multiple selection, for example, in order to align all the controls in the selection
(how to actually align the controls will be described below).
Adding to a Selection
When one control has been selected, you can add other controls to the
selection by holding down the Ctrl key when clicking to select them. As you
add controls, a box will appear around the complete selection and each control
in the selection will be marked by a circle in the upper left corner of its own
bounding box.
Marquee Selection
Another way to make a multiple selection is by marquee selection. When the
mouse cursor is in the design area but not pointing at anything (appears as a
cross), press the left mouse button and hold it down. When you drag the
mouse, a rectangle will appear–a marquee. As the rectangle expands, any
control that it overlaps, completely or partly, will be selected; there will be a
circle in the upper left corner of its bounding box. Release the mouse button
when you have finished selecting controls.
Controls can be added to the selection individually (as described above), and a
marquee selection can be added to an existing selection by holding down the
Ctrl key while you perform the marquee selection.
controls–the text box holding information that can change during program
execution and the label holding static information (usually a caption for the
text box), changeable only during form design. The label is said to be a child of
the text box.
When a control branch, such as a text box with a label, is selected, the control
itself is displayed in a bounding box with sizing handles, as usual. The child
controls that are part of the control branch are marked by a box with a circle in
the upper left corner, and the whole branch is surrounded by a dotted
emphasis frame. If you click on the emphasis frame (the cursor changes into a
selection cursor as it touches the frame), the child controls will be added to the
selection; this turns the selection into a multiple selection that can be moved
as a whole.
Moving Controls
When the selection cursor appears, you can move the control below it by
pressing the left mouse button and holding it down while you drag the control
to the desired position. The control will be dropped when you release the
mouse button.
mouse button, drag the control to the desired position, and then release the
mouse button.
Multiple selections are moved as a whole and their relative positions within
the selection are not changed.
Aligning Controls
If you created a form without using a wizard–or if you did use a wizard but
rearranged some controls afterwards–you may want to align the controls more
accurately than it is convenient to do freehand with the mouse. C/SIDE
provides two methods for aligning controls easily and accurately:
1 You can turn on the option Snap to Grid in the Format menu. When you
move a control as described above you will notice that it is not moving
smoothly, but rather in small, fixed increments. The dots in the design area
represent some of the actual grid points that the controls snap to when they
are moved.
Hint: the distance between the grid points are properties (HorzGrid and
VertGrid) of the form. The unit is 1/100 millimeters.
To resize a control:
2 Place the mouse cursor on a sizing handle. The cursor will change into a
sizing cursor.
3 Press the left mouse button and drag the control to the size you want. If
Snap to Grid is on, the sizing takes place in fixed increments, in a way
similar to the one described above for moving a control.
Beware that it is quite possible to reduce the size of the containing control so
that a contained control seems to be outside the container. However, it is still
considered part of the container. As no part of it is inside the control, however,
it cannot be selected. The remedy for this is to enlarge the container so all
contained controls are inside it.
To save a form:
1 When you are closing a form, C/SIDE will ask whether you want the form to
be saved. If it is a new form (a form that has not been saved before) you will
have to assign an ID and a name. The ID must be unique and follow the rules
for numbering objects–your NTR will provide you with this information.
Hint: if you enter ID and Name as form properties, these values will be used,
and you will not be prompted for ID and Name when you close the form.
You can save a form without closing it by choosing Save or Save As... from the
File menu. You can use Save As... to give a form a new name.
Compiling a Form
Forms, like other objects in C/SIDE, must be compiled before they can be run.
As described above, you can choose to compile a form whenever you are
saving it.
While you are designing a form, you may want to test-compile it to find
possible errors (this is useful when the form contains C/AL code in triggers, as
described in Chapter 9). You can test-compile a form during design by
choosing the Compile option from the Tools menu.
Running a Form
In a finished application, your forms will be incorporated into menus or they
will be called from other forms. However, while you are designing forms, you
will often want to run them before they have been integrated into an
application.
You can run a form from the list of forms in the Object Designer window by
selecting it and clicking the Run button. (Note that forms can also be run from
inside the Form Designer by choosing Run from the File menu.)
Each field on the Property Sheet contains a value that you can set by entering a
value in the Value field on the Property Sheet. As soon as you leave the field
(by hitting Enter or by moving with the arrow keys) the property is updated. If
what you entered contains an error (for example, if you accidentally changed
the ID of one control to be the same as that of another control), the update will
not be accepted.
Default values are displayed in angle brackets (<>). If a property has a default,
you can reset it to the default by deleting the current value and then moving
out of the field. Notice that some properties do not have defaults–mainly
those that describe the position of the control within the form. These
properties are constantly updated by C/SIDE when the control is moved.
validation. For example, if the field property that determines which characters
the user can enter is set to lowercase only, you cannot use the properties of
the control to reset it to also accept uppercase characters. You can narrow the
accepted range of characters but not broaden it. On the other hand, you can
change properties like the caption–as this property has nothing to do with
data validation.
When you design an application, you must consider whether these common
properties should be specified at the field level or at the control level. The
advantage of using the lowest level (the field level) is that whenever the field is
used as the data source of a control, these settings will be used as defaults.
This ensures consistency.
Form Properties
The table below briefly describes some of the more important form properties.
All properties are described in detail in the online Reference Guide. You can get
context-sensitive Help for a property by opening the Property Sheet for a form,
placing the cursor on a property and pressing F1.
Static controls
Static controls are controls that cannot change contents at run time.
Label A label is used for displaying text, most commonly for displaying the
caption of another control. In this situation the label is normally–and
conveniently–a child of the other control, but labels can also be used as stand-
alone controls.
Data controls
Data controls are controls that can display the value of a C/AL expression, for
example the value of a table field or a variable (the simplest expression
perhaps is just the name of a table field or a variable) or of a ‘real’ expression.
The valid combinations of data control and data type are as follows
Containers
Container controls are used for grouping other controls. Some properties of
the container overrule the same property in the contained controls: if the
container is not editable, no single contained control can be edited (even if it
individually has the Editable property set to TRUE).
Tab Control A tab control can be thought of as a kind of book with several
pages, or as several frames, where only one is visible at a time. The user can
switch between pages by clicking on tabs with captions.
Data Containers
Table Box A table box is a container, too, but a special kind. It contains
repeated data controls and is used to create columnar tables. Each data
control contained by the table box constitutes one column for which a static
control is used as a heading. The rows arise from vertically repeating each data
control. (If the table box displays records from a table, each row displays one
record.)
Other
Command Button A command button is not related to data–it performs an
action when it is ‘pushed’, that is, when it is clicked, or when Enter or Space is
pressed while the button has the focus.
Menu Button A menu button can be clicked just like a command button, but it
does not perform an action: when you click it, a menu opens containing a
number of menu items that you can choose.
Menu Item The lines in a menu that can be chosen are called menu items.
Each menu item resembles a command button: it can perform an action when
you click it.
The Toolbox
You use the Toolbox to insert controls. The Toolbox is opened by choosing
Toolbox from the View menu. You select a specific tool by clicking the
corresponding icon.
(not in use)
Lock Add Label
Note that some of these tools are not implemented in the present version of
C/SIDE, but that the icons are already present–they will, however, always
appear disabled.
When you click the Pointer tool, the state changes from insertion to selection.
(You can use this if, for example, you change your mind about inserting a
control.)
The Lock tool locks the current control selection. Normally, after you have
inserted a control, you have to select the type of control again before inserting
the next control. You can continue inserting controls of the same type without
having to select the tool again and again by turning Lock on (it is a toggle).
If Add Label is on (it is a toggle), all controls will have a label when you insert
them.
When you move the cursor into the design area, it will change into the Control
Insertion cursor.
(At present you need to activate the Form Designer window–for example by a
mouse click–before the cursor will change into the Control Insertion cursor).
4 Click in the design area for each selected field to insert a text box at the
cursor position.
If you selected more than one field, the text boxes will inserted aligned in a
column below the mouse position.
· The default settings for the Name and Caption properties are the same as
the setting for the Name property of the underlying table field.
· In general, all properties that are both field properties and text box
properties have the value of the field property in the underlying table as a
default value.
· The text box has a label with a caption that defaults to the caption of the
text box.
The advantage of using the Field Menu to add text boxes with labels is that you
are effortlessly assured that naming and properties are consistent.
Beware that if the data type is anything but boolean, a text box will be created
automatically. If the data type is in fact boolean, a check box will be created.
4 Click to add a text box of the default size, or click and drag to create a text
box with a different size.
Now you have an unbound text box control on the form. Notice that no
characteristics were inherited and that the text box has no label.
Subsection Changing the Properties of a Control on page 121 explains how you
can bind the text box to a table field and add a label, and subsection Displaying
a Calculated Value on page 126 tells how you can use the text box to display a
value that is calculated on the fly.
To add a label:
4 Click to create a label of the default size, or click and drag to create a label
of a different size.
5 As the label is not part of a control branch, it will be given a default name
and caption (like Control4). You can change the name and the caption on
the Property Sheet for the label (see Changing the Properties of a Control on
page 121).
When a control that has color properties is selected, you can pick colors for
foreground (text), background and border by clicking in the palette. The
corresponding properties are ForeColor, BackColor and BorderColor.
The check boxes Background and Border are used to toggle the display of
background color and display of the border on and off. The corresponding
properties are BackTransparent and Border (if these options are off, a
background or border color will not have any effect).
If the control has a border, the nine buttons at the bottom can be used to select
border style and border width.
When a control that can display text is selected, you can set the font properties
by using the tool. You can enter the font name, the font size, attributes (bold,
italic and underline) and the horizontal alignment of the text (left, center, right
and general–general meaning that text is left-aligned and numbers are right-
aligned).
When you use a form wizard or the Field Menu to create a text box that has a
direct relationship to a table field, Name and Caption will be set by default to
the name of the table field (unless the table field has a Caption: in this case,
this Caption is used). The label has a Caption derived from the Caption of the
parent control (the text box). You can supply a Caption in either place if you
want to have a different, perhaps more descriptive, text than the field name as
a caption.
· If you change the Caption property of the text box, the Caption property of
the label will be set to this value as a default (you will see that it is displayed
in angle brackets). If you change the Caption property of the text box again,
the Caption property of the label will also be changed again.
· On the other hand, if you change the Caption property of the label directly,
you will notice that the value that you enter is displayed without angle
brackets, signifying that it is no longer a default value. This means that if
you change the property of the text box, the value here will no longer be
updated.
All it takes is to change the SourceExpr into what you want. If it is the name of a
field in the database table, the values for Name and Caption automatically
default to the standard values for a bound control, that is, they default to the
name of the table field.
This will not automatically add a label to the text box, but you can add one, as
described below.
The control branch resulting from this operation can be selected and moved as
described in section 6.3, Selecting, Moving and Adjusting Controls.
Display Properties
Controls that you add to a form–either by using a wizard or manually–will have
a default set of properties that define how the control itself and the data it
displays are formatted. While this ensures a consistent visual design
throughout your applications, it cannot provide for all needs. You may
therefore have to change some properties that affect the way your forms and
their controls are displayed.
DecimalPlaces This property (whose setting specifies both the minimum and
maximum allowed values) determines how many decimals are displayed and
how many can and must be entered. A typical situation where this property
would be used is when amounts are stored in the database with 5 decimal
places for higher precision, while you want the user to see only the customary
number of decimal places for the currency in question–for example 2. The
table field would then have the DecimalPlaces property set to 2:5, while the
DecimalPlaces property of the text box should be set to 2:2.
BlankZero The default is No. If you change it to Yes, zero values and booleans
that would have been displayed as a No will be blanked out.
Format This property defines how the system formats the SourceExpr of a text
box. For each data type, there is a default. There is also a set of standard
formats that you can select. Finally, you can build your own formats to serve
special needs.
HorzAlign and VertAlign These properties define how data in a text box or a
caption on a label will be aligned horizontally and vertically, respectively.
MultiLine If this property is set to Yes, labels and text boxes can have
multiple lines of text. The default is No with one exception: the label of a
column in a table box will have this property set to Yes. See subsection
Displaying More Than One Line of Text on page 126 for details.
PadChar This property specifies the character to be used to pad a string. The
character will be added to the left or right, or both, depending upon the text
alignment defined by the HorzAlign property.
LeaderDots This property specifies whether there will be leading dots before
the data. The dots are placed according to the horizontal alignment of the data:
if left-aligned, the dots are placed to the right, if right-aligned, the dots are
placed to the left–and if centered, there will dots both before and after the
data.
Numeric This property restricts input to numeric values only if it is set to Yes.
MinValue, MaxValue Sets a minimum or maximum value that the user can
enter.
ValuesAllowed Here you can specify the values that the user is allowed to
enter. Enter the values separated by semicolons, like 1;7;4711 or a;b;c.
CharAllowed Here you can enter characters that the user can enter. You can
enter a range, for example AZ, to limit entry to uppercase characters only, or
several ranges, for example amot, specifying two ranges: a to m and o to t.
NotBlank If this is set to Yes, then an entry consisting of nothing, one blank or
several blanks (spaces) will not be accepted–though a blank can be part of
string that contains other characters.
AutoEnter If this is set to Yes, the system will accept a user entry when the
maximum number of characters allowed has been entered into a table box, and
it will then move the focus to the next control–that is, the user does not have
to press Enter.
PasswordText If this is set to Yes, user input will not be displayed, but shown
as asterisks (******).
ToolTip If you enter a text here, it will be displayed in a small pop-up window
whenever the mouse cursor rests on the control for a short while. The text is
supposed to be a short, perhaps just one word, description of what the control
is used for.
3 Open the Property Sheet for the text box and set the MultiLine option to Yes.
4 Run the form. Entering or editing text will still take place on one line that
scrolls horizontally. When the focus is not on the text box, the contents of
the field will be formatted in multiple lines. Automatic line breaks occur only
after a space character, and the user can insert line breaks (‘hard newlines’)
by embedding a backslash character (‘\’) in a text string. (To display a
backslash, enter ‘\\’.)
5 You may have to experiment with the vertical resizing of the text box to find
the size that suits your purpose best.
when all the information needed for the calculation is actually stored in the
database, and–conforming to the rules for a relational database system–the
calculated value is not stored separately. However, the users of the application
do sometimes need this value. Adding a calculated control can give this
information, without violating the rules for good database design.
2 Choose a tool that inserts an appropriate data control (check box, text box,
indicator) in the Tool Box.
5 Open the Property Sheet for the control. Type the expression you want as
the SourceExpr property.
Example
You have designed a table with a field that contains the Unit Price of an item, and another field that
contains the Employee Discount Rate. On the form, you want to see the price that an employee
actually has to pay. Add an unbound text box and enter as the SourceExpr:
In C/SIDE you can present these options in several ways. The following
sections show two different approaches.
2 If the option text box is based on a table field, open the Field Menu and
highlight the field. Otherwise, proceed to create an unbound text box.
3 Choose the text box tool; then click in the design area to create the text box
(if the text box is based on a field that you have selected in the Field Menu,
just click in the design area).
4 If the text box is unbound, bind it to the variable now by entering the name
of the variable as the SourceExpr of the text box.
When you run the form, the text box will have the drop-down symbol attached
(h), and you will be able to open the list by clicking this symbol.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
you can enter only options that have been defined on the Property Sheet of the
field or variable. The first option will be displayed in the text box. If the
OptionString property has a blank as the first option, the text box will
accordingly be blank. This does not mean that options that are not in the
OptionString can be entered.
In the OptionString property of the control, you can select a subset of the
options already defined for the field–you cannot add options.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
but the visual presentation is, of course, quite different. An option button
group looks like this:
The advantage of using an option button group is that the user of the
application can see all the available options and the currently chosen option at
a glance. The disadvantage is that an option button group takes up more space
on the form than a drop-down text box does.
3 Add an option button for each option in the OptionString of the field or
variable (the option buttons must be added as unbound controls).
4 Enter the field or variable as SourceExpr in the Property Sheet for each
button.
5 Enter one of the options from the OptionString as the OptionValue property
for each button.
Each button has the OptionValue as its caption. Because the option buttons
have the same source expression, only one of them can be chosen at a time.
When you choose an option by clicking on the button, any previously-chosen
button will be marked as not chosen.
Example
In the illustration above, the option button group has been embellished by adding two frames: the
group is contained by a frame with a raised border and no caption. The other frame is actually the
–
‘Title’ caption with the TopLineOnly property set to Yes, and Caption property set to Title.
2 If the check box will have a direct relationship to a table field, select the field
in the Field Menu. Otherwise proceed to create an unbound check box.
3 If you want a label attached to the check box, click the Add Label tool (check
boxes do not by default have labels).
4 Choose the Check Box tool; then click in the design area to create the check
box (if you have selected a field of type Boolean from the Field Menu, you
only have to click in the design area).
5 If the check box is unbound, bind it to the variable now by entering the
name of the variable as the SourceExpr of the text box.
2 Choose the Command Button tool; then click in the design area to add the
command button.
This will create the command button. The next step is to define the action
associated with the button.
3 Open the Property Sheet for the command button. The PushAction property
specifies what happens when the command button is pushed.
4 Open the drop-down list in the PushAction property value field. You will see
this list of possible actions:
6 In the RunObject property, open the look-up table of system objects, and
choose the object you want to run when the command button is pushed.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
not all settings of the PushAction property require additional information.
Some do, such as RunSystem, while others, such as Yes or No, do not.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Choose the Frame tool; then click and drag in the design area to create a
frame.
3 Create the controls you want to be contained by the frame in the usual way,
placing them inside the frame as you add them to the form.
If a frame is deleted, all controls contained by it are deleted. See Sizing and
Resizing Controls on page 104 for details on resizing container controls.
Using Shapes
The ShapeStyle property lets you choose from a number of shapes: rectangle,
rounded rectangle, oval, among others. You can adjust the width and the color
of the lines that the shapes are composed from by changing the BorderWidth
and BorderColor properties.
To create a shape:
3 Click and drag in the design area to add a shape of the desired size.
4 Choose a ShapeStyle and appropriate width and color in the Property Sheet
for the shape.
To add an image:
4 Open the Property Sheet for the image control, and enter the file name of
the bitmap in the Bitmap property.
Beware that the bitmap is not referenced but actually imported. This has the
advantage that you don’t need any external files for your application. On the
other hand, if you make any changes to the bitmap during application
development, you must update the imported copy by opening the Property
Sheet, selecting the Value field of the Bitmap property and pressing F2. This
will cause a reevaluation of the field, thus forcing the bitmap to be imported
again. The size of the bitmap is limited to 32 Kb.
A picture box provides another advantage: it can display pictures that are
stored in BLOB fields. A BLOB field can have a size of up to 2 Gb.
3 Click and drag in the design area to create a picture box control.
To create a list of bitmaps from which one can be selected at run time for
display, follow these steps:
1 Enter a comma-separated list of the file names of the bitmaps you want to
use. The system provides a series of standard bitmaps that can be chosen
by entering a number–see the online Reference Guide entry on Bitmap for
details.
Enter the field name of the BLOB field as the SourceExpr property. Do not
enter a BitmapList property.
They all have a property called Bitmap. Here you can enter the filename of a
bitmap. The maximum size of the bitmap is 32 Kb, and it is actually imported,
not referenced (thus if you change the original bitmap, you will have to
reimport it).
They also have a property called BitmapPos, where you can select the
alignment of the bitmap within the control. This is especially useful when you
are combining a caption and a bitmap: then you could right-align the caption
and left-align the bitmap, like the command button in picture below.
Using a bitmap on a button or a check box can in some situations make the
user interface more intuitive: a well-chosen picture may be easier to remember
and use than a label.
To create an indicator:
2 Choose the indicator tool, and click and drag in the design area to create the
Indicator.
3 As the SourceExpr of the Indicator, enter the value you want to control the
indicator.
5 Set the Percentage property to choose whether or not the indicator will
display percentages. If this property is Yes, the “23%” shown in the picture
above will be displayed–otherwise, it will not. The gauge itself is the same
when Percentage is No and when it is Yes. (The percentage is calculated as
((value of SourceExpr) – MinValue) / (MaxValue – MinValue))*100).
You can use a form wizard to create a form with a tab control.
2 Choose the Tab Control tool and click and drag in the design area to create a
tab control.
3 Open the Property Sheet for the tab control, and create the pages you need
by entering a name for each page as a comma-separated list in the
PageNames property. The names will be used as captions on the tabs.
4 The tabs are created while you are in the Form Designer. You can select
pages by clicking the tabs.
5 Add controls on the pages. You can think of each page in a tab control as a
frame and add controls as you would in a frame.
You can use a form wizard to create a form with a table box.
2 Choose the table box tool, and click and drag in the design area to create a
table box.
3 If the form is not related to a table, you can establish a relation now, by
setting the SourceTable property of the form to the name of the table.
5 Select the fields you want in the table box from the Field Menu and click
inside the table box. A column will be added for each field, and each row will
display a record from the table. A label, derived in the same way as a label
for any text box, is added as a column heading.
For example, suppose you are designing an application that handles sales
orders. There can be many items on one single sales order, but one specific
item can only be part of one sales order. Some of the information on a sales
order, for example the address of the customer, is per order, while other
information, for example the item number, is per item. In a well-designed
database, with no redundant information, this means that the information on a
sales order is stored in two tables: one, a header table, with the general order
information, another, a lines table, with the information about each item. There
is one-to-many relationship between the tables.
However, the users of the application need to view information from both
tables at the same time: the header information together with the lines, like
this
Although this looks like a normal form it is, in fact, two forms.
The main form is the one side of the one-to-many relationship; in the example,
it is based on the Sales Order Header table. The subform is the many side of
the relationship; in the example, it is based on the Sales Order Line table.
When the user selects a sales order header in the main form, the subform is
updated to display only sales order lines pertaining to this sales order header:
there is a link between the main form and the subform that keeps the
information synchronized.
In order to add a subform, you will add a subform control. The subform control
establishes the link between the main form and the subform, but it is not a
form in itself. You can, however, display any form in the subform control.
If you are going to use an existing form as the subform, follow the procedure
described below. If you are going to create a new form to use as a subform, it
may be more convenient to create the subform first. How to do this is
described in the next subsection.
2 Select the Subform tool, and click and drag in the design area to create the
subform control.
4 Enter the name of the form that you want to use as a subform as the
SubFormID property (or use the lookup button to choose from a list of all
forms).
5 Enter the expression that links the two tables (for example the field that is
common for the tables) as the SubFormLink property. There is an assist-edit
function available to assist you (click the k button to open the assist-edit
window). Choose the field name from the many side of the relationship (the
subform table) as the Field. Then choose FIELD as the Type of the
relationship. Finally, choose the field from the one side of the relation (the
main form table) as the Value.
6 In the SubFormView property, you can specify the key, sort order and table
filter you want the system to apply to the table when it is displayed in the
subform (it is not mandatory to enter anything).
Example
In the SubFormLink property, you can choose other types of link than FIELD. If you choose CONST,
Value must be a constant expression that selects records where the Field matches this expression. If
you choose FILTER, Value must be a filter expression (as, for example, 10|30..40).
The typical choice is a tabular form, that is, a form with a table box. See
Creating a Table Box on page 138 for details. The table box should fill out the
form completely, and the HorzGlue and VertGlue properties of both the table
box on the subform and the subform control on the main form should be set to
Both–in this way, the subform and the table box will be resized when the main
form is resized.
· It can be difficult to get the sizing of the subform control on the main form
and the size of the subform itself right. You should finish the design of the
subform first. Get the values for width and height of the form from the
Property Sheet. Then, in the main form, click and drag a subform control of
any size. In the Property Sheet, insert the width and height of the subfom as
the width and height of the subform control.
· Generally, if the subform is a tabular form, it will look better if you let the
table box completely fill out the form vertically–this way there won’t be
extra space around the table inside the subform control–and set the
HorzGlue and VertGlue properties to Both.
· If the subform is a tabular form, you should size the form to show only a few
records at a time. Then, in the main form, set the VertGlue and HorzGlue
properties of the subform control to Both. The user can resize the main form
vertically and horizontally, and the subform will be resized along with it:
more records and fields will be displayed.
Suppose, instead, that you are designing a form that is bound to a table
containing information about customers. Some of this information is unique
for each customer, while other information is not. The names and addresses of
the customers are unique, but suppose you want to store information about
the shipper that is normally used for deliveries to each customer? There are
only a few shippers, and it would be redundant and in violation of relational
database design rules to store information such as addresses of these
shippers in the customer records.
Instead, you would create a Shipper table, and use a Shipper Code field to
create a link between this table and the Customer table, storing only the
shipper code in each customer record, and storing all other information about
the shippers in the Shipper table.
Name Name
Address Address
Customer Record
Phone ...
Number
.... Shipper Code
Name
Address
Customer Record
Number ...
NameShipper Code
Address
...
Shipper Code
This is, in fact, the many side of a one-to-many relationship: while each
customer can be associated with only one shipper, a shipper can be associated
with many different customers. A main form/subform is not applicable here (it
would be, though, if you were to design a form to display information about
the shippers–then the subform could display a list of customers that use each
shipper).
There are two things you must consider when creating the customer form (and
table):
· Do you want to provide the user with an easy way of entering the shipper
code?
· Do you want to validate the Shipper Code field in the Customer table
against the Shipper table? That is, do you want the system to verify that the
contents of the Customer table field are present in the Shipper table?
If you do not establish a relationship to the Shipper table, the users will have
to memorize the shipper codes, and they may easily enter a code that does not
exist in the Shipper table. If the tables are related, the system provides a
lookup function into the Shipper table, so that the user can press F6 or click a
lookup button (p) and select the code from a list that displays the codes as
well as other information such as name and address. A control can be related
to a field in another table by defining the relationship, either at the table level,
as a property of the shipper code field in the Customer table, or at the form
level, as a property of the text box displaying the shipper code on the Customer
(main) form.
If you want to make certain that the user does not enter non-existent shipper
codes into the Customer table, the system can validate the entries against the
Shipper table. The ValidateTableRelation property, either of the field (at table
level) or of the text box (at form level), governs whether entries are required to
exist in the Shipper table.
Apart from simply asserting that the entered codes exist in the Shipper table,
you can create more advanced validation rules that check the entered codes
against combinations of values of fields in both tables (for example, you can
have the system check whether the shipper allotted to a customer operates at
all in the customer’s country). To do this, you will have to create the validation
rule by writing C/AL code in the OnValidate trigger of the control on the main
form.
A form with a lookup on the Shipping Agent field looks like this when the
lookup function has been activated:
2 In the Value field of the TableRelation property, click the assist-edit button.
3 In the assist-edit window, enter the name of the table to lookup into in the
Table field (or choose from list that appears when you click the lookup
button).
4 In the Field field, enter the name of the field in the table (or choose from the
lookup list).
You can use the Condition and the Table Filter fields to create a more advanced
relationship than this basic one.
By using the Condition field, you can, for example, lookup to different tables,
depending upon the value of a field in the current table. Each condition line
corresponds to a statement in an if then...else if sequence.
In the Table Filter field, you can set a filter on the lookup table.
Validating Entries
Entries can be validated against the contents of a field in a related table quite
easily. If you set the ValidateTableRelation property to Yes–either at field level
or at control level–only entries that exist in the related table will be accepted.
If you need a more advanced validation, you can write C/AL code in the
OnValidate trigger of either the control or the field.
The rules for determining which lookup function is performed are these: a
trigger at the form level takes precedence over one at the table level. Both of
these take precedence over the system default action.
Pay attention to the fact that if no lookup form is defined (either at table level
or at form level), then although the text box will have the lookup button (p)
attached, a lookup will not be performed when the button is clicked.
If you are writing your own lookup function in the OnLookup trigger, you will
have to explicitly run a form by using the RUNMODAL C/AL function.
Hint
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
If you always design a basic tabular form (fast and easy, using the wizard) for a
table, and enter this form as the LookupForm (and DrillDownForm) of the table,
you will never forget to provide a lookup form. If you later on decide that this
form is not adequate for some lookups, you can add customized forms as
control properties.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Permanent Assist
This is a control property. If it is set to Yes, the lookup button will be
permanently displayed; otherwise, it will be displayed only when the control
has the focus.
A lookup form must be defined, either at table or at form level, just as when the
lookup is to another table. You cannot set conditions and filters, however (as
you can when the lookup is to another table). The default behavior is to display
all records in the table. If you need to change this, you will have to write your
own lookup function in the OnLookup trigger.
You can provide the same functionality by using the LookupTable action
(applicable to command buttons and menu items). In this way you can provide
both types of lookup on the same form: lookups to related tables from text
boxes, and lookups to the source table from command button actions.
In the first picture below, the Chart of Accounts, you can execute a drill-down
function in the Net Change field–a FlowField that summarizes transactions in
this account. The next picture shows the form that is displayed when that
particular drill-down is performed–a detailed list of the transactions.
Defining a Lookup Form on page 147), either at the table level or at the form
level.
Drill-downs resemble lookups in many ways, and with them you can do most of
the things that you can do with lookups–one exception being that drill-downs
pertain only to FlowFields, which have to be defined when the table is
designed.
· You can disable the drill-down altogether by setting the DrillDown property
of the text box explicitly to No.
· Text boxes based on the same FlowField will have different drill-down forms
if you define separate DrillDownFormIDs at the form level.
· You can decide whether the drill-down button should be displayed
permanently or only when the text box has the focus, by setting the
PermanentAssist property of the text box. Yes means that the button will
always be displayed, and No means that the button will be displayed only
when the text box has the focus.
· You can change the drill-down behavior altogether by writing C/AL code in
the OnDrillDown trigger of the table field or the control. In this case you
have to run a form explicitly from your trigger code, as the system does not
perform any part of the default drill-down and does not display a form
automatically.
A better solution is to provide a way to launch the customer form directly from
the sales order form, automatically select the appropriate customer record,
update and close the customer form, and continue filling out the sales order
form.
In order to launch another form, you can add a control that has a PushAction
property and run the customer form with parameters to select the correct
record from the Customer table whenever the user ‘pushes’ the control. You
can use command buttons, menu items, check boxes or option buttons.
3 Set the RunObject property of the command button to the name of the form
you want to launch. As you can use RunObject to run any object, you have to
specify the type of object (Form, Codeunit, and so forth). You can choose the
object from the lookup list that is provided (in this case, the type of the
object is inserted automatically).
4 Set the RunFormLink property to establish the link to the form you want to
launch. Use assist-edit (click k ) to create the expression. First, select a field
from the table underlying the form you going to launch. Choose FIELD as the
type of the relationship. Finally, as the Value parameter, select the field in
the table underlying the current form that must match the value in the other
table.
· Perform an action when clicked. This can be an action from the same set of
actions as command buttons (see the online Reference Guide for a list), or it
can be an action written in C/AL, as menu items have OnPush triggers just
like command buttons.
· Contain a submenu that is opened when the line is clicked.
· Be a separator–a line used for grouping items in a menu together.
A menu is created in two steps. First you add a menu button to your form. This
part is exactly the same procedure as adding a command button. Then you
open the Menu Designer for the menu button and create the menu items.
2 Choose the Menu Button tool, then click in the design area to add the menu
button.
3 Select the menu button and open the Property Sheet for the menu button.
As a menu button does not have a relation to data–field or variable–Name
and Caption are set to default values (like Control7). Change the Caption to
an appropriate text. If the text contains an ampersand (&), the system
interprets the following letter as an access key.
1 Select the button and open the Menu Designer while the button is selected.
(Choose View -> Menu Items.)
2 The first field, Visible, is by default set to Yes. Leave it like this.
3 Add lines by filling out the Caption field. If you do not create an access key
yourself (by embedding an ampersand in the Caption text), the system will
automatically use the first letter of each Caption as an access key. If you add
menu lines where some captions start with the same letter, you must set the
access keys yourself to avoid overloading certain ones.
4 If you want, you can define a shortcut key (accelerator key) by entering the
name of the key in the ShortCutKey field. Keys are entered as follows
Key Entered as
Function keys F1, F2, F3, ...
Control, Alt, Shift Ctrl, Alt, Shift
Other keys A, B, C, ... (these keys must be part of a key combination with Ctrl or
Alt).
Key combinations for example: Ctrl+A, Shift+F2
An accelerator key is active as long as the focus is on the form that the menu
button is a child of. Beware of accidentally overloading some key
combinations so that they perform different actions when different forms
have the focus–this could confuse the user. Also beware of using
accelerator keys that the system already uses.
5 Enter the action for the menu item in the Action field. You can use the drop-
down list that is available to choose from among the same actions as for a
command button. You can also write C/AL code in the OnPush trigger of the
menu item.
6 If you have chosen RunObject, you can define the object (form, report,
codeunit) in the Object field. There is a lookup function available to help you
select the object. For other parametrized actions (for example RunSystem)
you have to set the parameters in the Property Sheet of the menu item (see
below for details).
To add a separator to a menu, click the Separator button in the Menu Designer.
The separator will be inserted after the currently-selected line.
Submenus and Menu Levels Menu items can be nested, that is, when you
click a line on a menu, another menu can open.
Submenus are defined in the Menu Designer. When an item is selected, you
can indent it by clicking the right-arrow button. An indented item becomes a
menu item on a submenu. If you open the Property Sheet for a menu item, you
will see that when first created, menu items have the MenuLevel property set
to a default value of zero. As items are indented, the MenuLevel is set to 1, 2, 3
and so forth–one level for each click on the indentation button (you can cancel
indentation by clicking the left-arrow button–each click cancels one level of
indentation). There are a few logical rules you must follow when creating
submenus:
· If there are any items at all in a menu, there must be at least one item with
MenuLevel 0 (zero).
· Each MenuLevel can be at most one higher than the preceding level in the
list.
· If a higher MenuLevel follows a lower one (for example, 1 follows 0), the
menu item with the MenuLevel 0 becomes a submenu, and the item with
MenuLevel 1 becomes an item on this submenu. A menu line that is a
submenu cannot have any action associated with it.
· There can be up to 10 menu levels (numbered from 0 to 9).
You can see how this feature can be used in C/SIDE itself. In the Format menu,
the Snap to Grid menu item is a typical example: it can either be on or off.
When it is On, the check mark is displayed.
The following table outlines the full range of triggers. The column at the right
indicates the controls for which the trigger is relevant.
Controls are
1 - command button, 2 - menu button, 3 - check box, 4 - option button, 5 - text box, 6 -
picture box, 7 - indicator, 8 - subform, 9 - menu item
The table only sketches out the main purpose of each trigger. Refer to the
online C/SIDE Reference Guide for concise descriptions and details.
OnLookup is also a field trigger at the table level. The flow is different here:
when a lookup is requested, the system executes the control lookup trigger, if
defined, in place of the field lookup or system default. If no control lookup
trigger is defined, a field lookup trigger (if defined) replaces the system default
lookup function.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Select the form itself or the control (or menu item) in question.
3 Open the C/AL editor (choose C/AL Code from the View menu).
4 In the editor, you will only have access to those triggers that are relevant for
the object that you selected. Enter C/AL code in those triggers you want to
use, or modify those existing triggers you want to.
5 You can test-compile the form, thus including the code, by choosing
Compile from the Tools menu.
If you are not familiar with the C/AL programming language, you should read
Part 5, Codeunits, in this guide.
Report Description
Properties
Triggers
Data Items
Properties
Triggers
Sections
Properties
Triggers
Controls
Properties
Request Form
Properties
Triggers
Controls
Properties
Triggers
Report Description This is the total description of the report: how data is
collected, and how data is presented on paper when the report is run. The
report description is stored in the database.
Section In a printing report, each data item has one or more sections. A
section can be thought of as a block of information to print on the paper. The
complete report is composed of a number of sections, some that are printed
only once, for example a header, and some that are printed for each record that
is retrieved from the database.
Request Form A request form is a form that is run before the actual report
begins execution. It is used to gather requests and options from the user of the
report–for example, sort order or level of detail.
Trigger Certain predefined events that happen to a report cause the system
to execute a user-definable C/AL function–the event triggers the function. As
you can see in the diagram, the report itself, the data items, the sections, the
request form and the controls on the request form all have triggers. Triggers
are edited in the C/AL editor.
Defining the data model means defining how the data for the report is
collected. This includes
· defining the tables the report will use by creating data items.
· defining relationships between data items if the report uses more than one
table.
· defining the key, sort order and filters to use with the involved data items.
· defining how data is to be grouped.
· defining how subtotals and totals are to be calculated.
· possibly writing C/AL code in data item triggers to obtain advanced
functionality.
Data Items The data model of a report is built from data items. A data item corresponds to
a table. When the report is run (see the diagram on page 171) each data item is
iterated for all records in the underlying table. When a report is based on more
than one table, you establish a hierarchy of data items to control how the
information is gathered by indenting data items.
Example
In order to make a report that prints out a list of customers and for each customer lists sales orders
placed by that customer, you will define two data items: one that corresponds to the Customer table
and one that corresponds to the Sales Order table. The second data item is indented: as the report
works its way through the records in the Customer table, for each customer all sales orders that are
related to this customer must be found by going through the records in the Sales Order table.
Sections The visual layout of a report includes the sections. In a printing report
(remember that reports do not have to print anything), one or more sections
are attached to each data item. There are several types of sections, each
having a specific function. Normally, the bulk of the data is printed out in the
body section of a data item, while the header section of the data item is used
to print information before any record of the data item is printed (for example,
column captions), but there are reports–like some of the examples in this
guide–where the body section is not used at all, and all information is printed
in other sections.
Sales Statistics
CRONUS International Inc.
Total
The report above prints sales statistics information and retrieves all its data
from one table. It demonstrates a range of the features that are available for
designing reports
· After all records (all records that were selected by the filter, that is) have
been printed, a footer section is printed that contains totals for the selected
customers.
· In the body section and in the footer section, a filter has been applied to
create columns where data are collected and totalled for different periods.
Entry point
Exit
Report.Run
ReqForm.Run
Cancel
OK / Print / Preview
Call PreReport
Trigger
DataItem.Run
No more
Call PostReport
Trigger
1 When the user initiates the report run, the OnInitReport trigger is called.
This trigger can perform processing that would be necessary before any part
of the report is run–or stop the report.
2 If the OnInitReport does not end the processing of the report, the request
form for the report is run, if it is defined. Here, the user can choose to cancel
the report run.
4 When the OnPreReport trigger has been executed, the first data item is
processed (provided that the processing of the report was not ended in the
OnPreReport trigger).
5 When the first data item has been processed, the next (if any) data item will
be processed in the same way.
6 When there are no more data items, the OnPostReport trigger is called. You
can use this trigger to do any post processing that is necessary, for example
cleaning up by removing temporary files.
The flow chart below further explores step 4–how a data item is processed:
DataItem.Run
Header.Run
OK GroupHeader.Run
Call PreDataItem
Trigger
GetRecord Body.Run
No more
DataItem.Run
Call PostDataItem
Trigger Get next lower
OK
DataItem
No more
GroupFooter.Run
Footer.Run
1 Before the first record is retrieved, the OnPreDataItem trigger is called, and
after the last record has been processed, the OnPostDataItem trigger is
called.
2 Between these two triggers, the records of the data item are processed.
Processing a record means executing the record triggers and outputting
sections. C/SIDE also determines whether the current record should cause
outputting of a special section: header, footer, group header or group
footer.
3 If there is an indented data item, a data item run will be initiated for this
data item (data items can be nested 10 levels deep).
Properties and triggers for each of the data items can be edited by opening the
Property Sheet or the C/AL editor, respectively, while the data item is selected.
Properties and triggers for the report can be edited by selecting an empty line
in the Report Designer window and then opening the Property Sheet or the
C/AL editor, or by choosing Select Object from the Edit menu.
You can use the Field Menu–similar to the Field Menu in the Form Designer–to
select fields and place them in the sections as controls. In the picture below, a
number of text boxes and labels have been placed in four sections.
You can think of each section as one or more lines on the paper that the report
will eventually be printed on. A header section is printed only once, while a
body section typically will be printed several times as the report loop is
iterated. You can control whether the header will be printed when a page break
occurs while body sections of the same data item are being printed.
You can edit properties and triggers for each section by opening the Property
Sheet or the C/AL editor, respectively, while the section is selected.
The controls you place in the sections have a subset of the properties that
controls have on forms (as not all properties are relevant on a report), and you
can use the same tools to modify the properties (the Font Tool, the Color Tool).
You can see a list of the properties on the Property Sheet, and you can read
about them in chapter 7, Designing Forms, or in the online C/SIDE Reference
Guide.
You only have to use this designer if you want to prompt the user to select
options. When a report is run, the request form looks like this:
As you can see, a form with a tab control has been created. The first two tabs
correspond to data items. They are created automatically (though you can
control the contents by setting properties of the data items), and they are used
for setting filters and defining the sort order.
The third tab, Options, only appears when the Request Options Form Designer
has been used to create a request options form.
The form has the same properties and triggers as any other form, and the same
controls can be placed on it.
To save a report:
1 When you close a report, C/SIDE will ask whether the report should be
saved. If it is a new report (a report that has not been saved before) you will
have to assign an ID and a name. The ID must be unique and follow the rules
for numbering objects–your C/SIDE dealer will provide you with this
information.
Hint: if you enter ID and Name as report properties, these values will be
used, and you will not be prompted for ID and Name when you close the
report.
You can save a report without closing it by choosing Save or Save As from the
File menu. By using Save As, you can rename an existing report–thereby in
effect copying it.
Compiling a Report
Reports, like other objects in C/SIDE, must be compiled before they can be run.
As described above, you can choose to compile a report whenever you are
saving it.
While you are designing a report, you may want to test-compile a report, to find
possible errors (this possibility will be more important if the report contains
C/AL code in triggers, as described in chapter 11). You can test-compile a report
during design by choosing the Compile option from the Tools menu.
Running a Report
In a finished application your reports will be incorporated into menus, or they
will be called from, for example, a command button on a form. However, while
you are designing reports, you will often want to run them before they have
been integrated into an application.
Test-running reports While designing a report, you can test-run the report by choosing Run from the
File menu. In this way, the report will be compiled and run in its current stage
of development. It will not be saved, which means that you can use this
function to verify that the changes you are making work as intended before you
save them.
Running reports from You can run a report from the list of reports in the Object Designer main
the Object Designer window by selecting it and clicking the Run button.
· Report Properties
· Designing a SImple Report
· Designing a More Advanced Report
Chapter 10. Designing Reports
Default values are displayed in angle brackets (<>). You can reset any
property (for which there is a default) to the default by deleting the current
value and then moving out of the field.
Report Properties
The table below briefly describes the report properties. All properties are
described in detail in the online C/SIDE Reference Guide. You can get context-
sensitive Help for a property by opening the Property Sheet for a report,
placing the cursor on a property and pressing F1.
The Property Sheet for a report is opened by choosing Properties from the View
menu while an empty line is selected in the Report Designer window, or by
choosing Select Object from the Edit menu.
Property Meaning
ID ID of the report–must be unique among reports.
Name Name.
Caption Caption (shown on request form window, for example–default is Name).
ShowPrintStatus Should the printing status window be displayed during printing (with the
opportunity to cancel printing)?
UseReqForm Should the request form be run before the report is run?
UseSystemPrinter If Yes, then the system default printer is suggested as printer for the report. If
No, then the printer defined for the combination User/Report in the setup of
the system is suggested.
ProcessingOnly No printing–only processing. If Yes, the report cannot have sections.
Description Description–for internal purposes, as it is not user-visible.
TopMargin Topmargin in 1/100 mm.
BottomMargin Bottom margin in 1/100 mm.
LeftMargin Left margin in 1/100 mm.
Property Meaning
RightMargin Right margin in 1/100 mm.
HorzGrid Distance between horizontal gridlines (1/100 mm).
VertGrid Distance between vertical gridlines (1/100 mm)
Permissions The permissions of the report to access database objects. (The report can
have wider permissions than the individual user, thereby enabling the user to
print reports that retrieve information from tables that he or she cannot
normally access.)
Property Meaning
DataItemIndent Indentation level (can be set in the designer when creating data
items).
DataItemTable Table of item (can be set in the designer when creating data items).
DataItemTableView The key, sort order and filters to apply.
DataItemLinkReference The DataItemVarName of a less-indented Data Item that this DataItem
will be linked to.
DataItemLink Link between the current Data Item and the Data Item specified by
DataItemLinkReference.
NewPagePerGroup Should each group be printed on a separate page?
NewPagePerRecord Should each record be printed on a separate page?
ReqFilterHeading Tab caption for this item on request form (default is name of
DataItemTable).
ReqFilterFields Names of the fields that will be included in the ReqFilter form.
TotalFields Names of the fields for which totals will be calculated.
GroupTotalFields Names of the fields that will be used for grouping data.
CalcFields Names of the fields that will be calculated after a record has been
retrieved.
MaxIteration Maximum number of data item loop iterations.
DataItemVarName Name of record as variable (default is name of DataItemTable).
PrintOnlyIfDetail Print item only if sublevels generate output.
Section Properties
The table below briefly describes the section properties. All properties are
described in detail in the online C/SIDE Reference Guide. You can get context-
sensitive Help for a property by opening the Property Sheet for a Section,
placing the cursor on a property and pressing F1.
Property Meaning
PrintOnEveryPage Should header and footers be printed on all pages?
PlaceInBottom Should footer be placed below last line or at bottom of page?
SectionWidth Width in 1/100 mm.
SectionHeight Height in 1/100 mm.
Control Properties
Controls in reports have exactly the same properties as controls on forms–that
is, those properties that it makes sense to set in a report. The Property Sheet of
a control shows the properties, and chapter 7, Designing Forms, describes
each property, as does the online C/SIDE Reference Guide.
4 C/SIDE opens the Report Designer. Select the first data item field (by
clicking it), and choose a table from the lookup form that you open by
clicking the p button. The Name is by default set to the name of the table.
You do not have to change it in this report. In the example here, the
Customer table has been chosen, and the default for Name is “Customer”.
5 Open the Property Sheet for the data item by choosing Properties from the
View menu.
6 Select the DataItemTableView property and click the assist-edit button (k)
to open this window:
7 Choose the key, sort order and filters that you want to use. In the example
here, a key (previously defined during table design) consisting only of the
No. field has been chosen, and the sort order is set to Ascending. The Table
Filter field has been left empty, meaning that a permanent filter is not
defined on the table. After you enter your choices, press OK.
8 Select the ReqFilterFields property, and click the assist-edit button (k) to
open this window:
9 Select the fields on which the user will often need to set filters–you can use
the lookup function to select them. In the example here, the fields No. and
Country Code have been selected. When you have selected the fields, press
OK.
The picture below shows the request form the user will see when the report is
run (with the various choices made as in the steps above).
As the key and sort order were established during report design, the only
choice left for the user involves setting filters. The fields that were defined as
ReqFilterFields are shown, but the user can also choose to put a filter on other
fields by adding lines below those that are already used.
Concerning ReqFilterFields, you should be aware that the user can choose to
set filters on other fields than those you specify. However, it will still be a good
idea to add the fields that those who use the report will often want to set filters
on. If the table has a lot of fields, the casual user may find it difficult to find the
relevant fields to filter from a lookup list of all the fields in the table.
You can remove the filter-selection tab altogether by not defining any
ReqFilterFields for the data item and by setting the DataItemTableView to
define a sort order. If you create a request options form, it will still be shown.
If a DataItemTableView is not defined, the user will be able to select key and
sort order at runtime. Then, the request form will look like this:
When the Sort... button is pushed, the user can choose the key and sort order
from this form:
Be careful...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
about what you allow the user to change. In a more complex report, where you
work with data from several tables, the functionality may well depend on a
specific key and sort order. On the other hand, letting the user choose filters
freely will not interfere with the logic of the report. In a very simple report like
this one, you can select a key and define a sort order if you want, or leave it up
to user.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 Open the Section Designer by choosing Sections from the View menu while
the Report Designer window has the focus. Having created a data item as
described above, the Section Designer will look like this:
As you can see, a section named “Customer - Body” (‘1’ means that this is
presently the first section of this data item) has been inserted. By default, a
Body section will be inserted for each data item that has been created; these
sections will be in the same order as the data items in the Report Designer.
2 Now, insert a header section for the Customer data item. Choose Insert New
from the Edit menu. The following form will appear:
3 Choose Header as the Section Type and Before Current Section. Then press
OK. The Section Designer now looks like this:
4 Open the field menu by choosing Field Menu from the View menu. Select the
fields that you want in the report. (You can select multiple fields by holding
down Ctrl while clicking in the selection bar.) In the example below, four
fields have been selected.
5 Move the mouse cursor into the Body section of the data item. Click once to
activate the window–the cursor changes into the Control Insertion cursor.
Place the cursor at the left side of the section and click again. A text box with
an attached label will be inserted for each selected field.
6 Open the Property Sheet by choosing Properties from the View menu. Click
in the Header section. Look at the setting of SectionWidth (the unit of
measure is 1/100 mm).
All sections widths have been modified to make room for the inserted controls
(the default width, when no controls have been inserted, is 12000). In this
case, the resulting width is 15450, or 15.45 cm. If the report is going to be
printed on A4 paper this is perfectly acceptable–that paper is 21 cm wide.
7 Select all the labels as a multiple selection (hold down Ctrl while clicking),
and move them all into the Header section in one move (in this way, the
alignment of labels and text boxes will be preserved).
Now, the report is ready to be printed, but it still needs some work before it will
look good on paper.
8 If the report, when it is run, ends up consisting of more than one page, you
will want the Header section–containing the labels–to appear on every
page. Open the Property Sheet for the Header section and set the
PrintOnEveryPage property to Yes (see the picture in step 6 above).
9 Moving the labels out of the Body section has left this section too
high–there will be an empty line for each customer record that is printed. To
resize the Body section, move the mouse cursor into the Section Designer
until it touches the lower bound of the Body section and turns into the
vertical resizing cursor. Then click and drag the section upwards until it has
the same height as the text boxes.
10 Save and close the report, and run it from the Object Designer. The example
described so far gives this result with sample data:
As you can see, the label and the data in No. do not line up very nicely. This is
because both controls have their alignment set to General (the default). The
label is left-aligned because it contains text, while the text boxes are right-
aligned because they contain numbers.
11 Select the No. label and open the Property Sheet. Set the HorzAlign
property to Right.
The sample report that will be created uses two tables: one is the customer
table, as in the preceding example. The other table contains sales lines, lines
from not-yet-posted sales orders that contain information about the actual
items that have been ordered. There is a one-to-many relationship between
the two tables: while one customer can have many items on order, a sales line
can pertain to only one customer.
1 In the Report Designer window, choose the Customer table as the first data
item, and the Sales Line table as the second.
2 Indent the Sales Line data item by clicking the right-arrow button once while
the data item is selected:
.
The data model defined thus far will work like this:
· For each record in the Customer data item, the report will run through the
entire Sales Line data item.
This is clearly not the purpose of the report–you need a way to select only
those Sales Line records that are related to the current customer. This is
accomplished by the DataItemLink and DataItemLinkReference properties. The
DataItemLinkReference property points to a data item on a higher level (with
less indentation) and the DataItemLink property specifies a field in each data
item: here, records will be selected from the Sales Line table only when the
Sell-to Customer No. is the same as the No. in the Customer table.
3 Open the Property Sheet for the Sales Line data item.
5 In the value field of the DataItemLink property, open the form shown below
by clicking the k button.
6 In the Field field, enter the name of the field from Sales LIne (the more-
indented data item) that must correspond to a field from Customer (the
less-indented data item). You can use the lookup function to select the field.
7 In the Reference Field field, enter the name of the field from Customer that
must correspond to the field from Sales Line. Again, you can use the lookup
function to select the field. In the example below, the Sell-to Customer No.
field from the Sales Line data item and the No. field from the Customer data
item have been chosen.
8 Finally, open the Property Sheet for the Customer data item, and set the
PrintOnlyIfDetail property to Yes. This will cause the Customer body
sections to be printed only if there is data to print from Sales Line.
1 When you first open the Section Designer, there will already be a Body
section for each data item. Add a Header section for the Customer data
item.
2 Add fields to the Customer body section. Move the labels up into the Header
section.
So far, the procedure has been exactly the same as for creating the first, simple
report. Now, continue like this
4 At this point, you need to make a decision about the labels for the controls
of Sales Line: if they stay where they are right now, they will be printed for
each record of the data item. If a header section is added for Sales Line, this
header section will be printed each time the data item loop is entered,
which is for each record of the Customer data item. As neither of these
solutions seems very good, you can take a third approach: you can move
the labels into the header section of the Customer data item, like this:
5 Now, labels for both the Customer records and the Sales Line records will be
printed as column captions in the Customer Header section (remember to
set the PrintOnEveryPage property of this section to Yes). In order to make
the connection between labels and data clear, the labels for the Sales Line
columns can be changed to the normal font weight instead of the default
bold. Also, the text boxes of the Customer data item can be changed to bold,
to make these records stand out among the lines that are printed. (There
are bound to be a lot more records from Sales Line than from Customer.)
Furthermore, the Sales Line labels have been resized to occupy only one
line, and an empty line has been added to the header section.
6 Save and close the report, and run it from the Object Designer. The example
described gives this result with sample data:
The second report created in chapter 10 listed customers and those entries
from the Sales Line table that pertained to each customer. You can use
grouping and totaling to enhance this information in several ways.
Second, it would be useful to have a total amount per customer, showing how
much this customer has on order all in all.
1 In the Property Sheet of Sales Line (the indented data item), enter as the
value of the GroupTotalField property, the name of the field you want to be
used for grouping the records. You can use the assist-edit button to help
you select the field. Here, the No. field is used:
2 Use the assist-edit button for the DataItemTableView property, and then
select a key. You have to select a key that contains the field you want to
group by.
3 If the key you select is a composite key, the grouping can fail if there are
other fields in the key before the grouping field, and the contents of one of
these fields change. In other words: you may have to create a distinct key
for reports that access data in ways other those used by than your
application in general. For this report, a secondary key, consisting of the No.
field only, was created for the Sales Line table:
4 Enter the names of those fields for which totals should be calculated as the
value of the TotalFields property. You can use the assist-edit button to help
you select the fields. Here, the fields named Quantity and Amount are
selected:
This data model is now defined. This is what has been accomplished:
· For each record in the Customer data item, those records in the Sales Line
data item that are related to this customer are selected.
· The records from the Sales Line data item are grouped according to the item
number.
· Totals are maintained for the Quantity and Amount fields of the Sales Line
data item.
What, then, is the relationship between these totals and the sections–that is,
how can these totals be printed?
Until now, only Header and Body sections have been used. To print totals, you
will need to use some new sections. The table below gives an overview of all
types of sections:
In order to print out the totals, you will need to use both a GroupFooter and a
Footer section for the Sales Line (indented) data item.
In the GroupFooter section, the totals for Quantity and Amount will be for the
defined group–remember that the No. field was used for grouping.
When the entire data item has been iterated, the grand total can be printed in
the Footer section of the Sales Line data item.
1 For each record of the Customer data item, a loop for the Sales Line data
item is begun.
3 When the Sales Line loop ends, the Footer will be outputted. As the Body
section of the Customer data item was printed before any section of the
indented data item, the Footer is also the last section that will be printed.
Therefore, this section can be used to print summary information about the
customers.
That is, the Quantity and Amount totals for each item that a specific customer
has on order will be placed in a GroupFooter section of the Sales Line data
item, while the grand total for the Amount that the customer has on order will
be placed in a Footer section of the Sales Line data item (a Quantity total is
also maintained, of course, but this information is not too useful, since it will
be a total of quantities for all kinds of different items.)
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
properties of sections, such as PrintInBottom and PrintOnEveryPage, apply to
an entire data item. This means that you cannot, for example, have two Footers
for a data item, one for the ‘normal’ pages and one for the last page.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 Add a Header section for the Customer data item. This section will be used
to print headings for the columns in the report.
2 Add a GroupFooter section for the Sales Line data item. This section will be
used to print the summary information about each item.
3 Add a Footer section for the Sales Line data item. This section will be used
to print the summary information about each customer.
In this report, nothing will be printed in the Body section of the Sales Line data
item. Therefore, this section should be deleted. (You delete a section by
clicking on the section bar, then choosing Delete from the Edit menu. You will
be prompted to confirm the deletion.) The Section Designer will now look like
this:
4 Select the Body section of the Customer data item (by clicking the section
bar). Then open the field menu by choosing Field Menu from the View menu.
5 Select fields from the field menu. Here, four fields have been selected:
6 Click once in the Section Designer window to activate the window, then
move the cursor into the Body section of the Customer data item. Click to
insert text boxes and labels for the four selected fields.
7 Move the labels up into the Header section of the Customer data item and
adjust the vertical size of the Body section. Resize the labels vertically and
move them up to the top of the Header section. The Section Designer now
looks like this:
8 Select the GroupFooter section of the Sales Line data item (by clicking the
section bar) and open the field menu by choosing Field Menu from the View
menu.
9 Select fields from the field menu. Here, the No., Description, Quantity and
Amount fields have been selected. Insert the fields in the GroupFooter
section of the Sales Line data item. Delete the labels, and resize the section
vertically.
10 Select the Footer section of the Sales Line data item. Insert the Amount field
here and remove the label. Let the section have its default size–this way,
there will be some empty space before each new customer.
9,495.90
Obviously, this report needs some work before it looks good and is truly
functional. For example, we need to devise a way to place captions for the
columns from the indented data item. But the logic works: for each customer,
there is a list of items where quantities and amounts have been summarized,
and the total amount for each customer is also calculated.
One desirable improvement is to add a line at the end of the report where the
grand total for all customers is printed. To do this, however, it is necessary to
use C/AL code in a report trigger. The Advanced Sample Reports section on
page 214 gives examples of how to do it.
Report Triggers
These triggers pertain to the report itself.
Trigger Executed
OnInitReport When the report is loaded.
OnPreReport –
Before the report is run but after the RequestForm has been run.
OnPostReport –
After the report has run but not if the report was stopped manually or by the
Break function.
The table only sketches out the main purpose of each trigger. Refer to the
online C/SIDE Reference Guide for concise descriptions and details.
Trigger Executed
OnPreDataItem Before the data item is processed, but after the associated variable has been
initialized.
OnAfterGetRecord When a record has been retrieved from the table.
OnPostDataItem When the data item has been iterated for the last time.
The table only sketches out the main purpose of each trigger. Refer to the
online C/SIDE Reference Guide for concise descriptions and details.
Section Triggers
These triggers pertain to each of the sections of a data item:
Trigger Executed
OnPreSection Before processing a section.
OnPostSection After processing a section but before printing it.
The table only sketches out the main purpose of each trigger. Refer to the
online C/SIDE Reference Guide for concise descriptions and details.
The Date table will be used to create a report that writes out information from
the Cust. Ledger Entry table (the Customer Ledger Entry table, but the word
Customer has been abbreviated). For each day in a range of dates (that can be
chosen by the user), the report summarizes entries made on that date. For
each type of document (Invoice, Payment, and so forth), a line will be printed
containing the number of documents of this type and the sum of the amounts
of these lines. After each date, the total number of entries made on that day,
along with the total amount of all these entries, will be printed. Finally, the
total number of entries and the total amount for the selected date range will be
printed at the end of the report.
You could create the report by grouping according to the Cust. Ledger Entry
table alone, but the field that contains the posting date in that table is not part
of any key. Creating a special key just for this report is not desirable, because it
would slow down all other transactions involving this table–in fact, all entries
concerning sales would be affected.
1 Open the Report Designer and create two data items with the Date and the
Cust. Ledger Entry as the underlying tables. Indent the Customer Ledger
Entry data item.
2 Open the Property Sheet for the Date data item. Then use the assist-edit
button to help you set the value of the DataItemTableView property so that
it selects records whose Period Type is Date. This is an important step, as
the iteration of the Date data item would otherwise run through all records,
including those for Weeks, Quarters, Months and Years.
r
3 Enter the Period Start field as the ReqFilterFields property of the Date data
item in order to let the user select a range of dates at run time.
4 Open the Property Sheet for the Cust. Ledger Entry data item. Then use the
assist-edit button to help you set the value of the DataItemTableView
property to an appropriate key. An appropriate key in this case is a key
containing the Document Type field, as the definition of a group will be
based on this field. Here, a key is selected that has this field as its first
component. As the report is going to show only summarized information,
rather than all the entries, the other fields that are included in the key are
not significant. (No individual entries will be printed, so they do not need to
be sorted in any specific order.)
5 In the Property Sheet for the Cust. Ledger Entry data item, set the
DataItemLinkReference property to point to the Date data item (this is the
default). Then use the assist-edit button of the DataItemLink property to
specify the field that establishes the link between the two data items.
Choose the Date field from the Cust. Ledger Entry data item, and the Period
Start field from the Date data item.
6 Enter the Document Type field as the value of the GroupTotalFields property
of the Cust. Ledger Entry data item, and enter the Amount field as the value
of the TotalFields property.
· The user can select a range of dates from the request form of the report.
· The report will run through the Date table, with a constant filter on the
Period Type field that selects only records whose type is Date. If the user
selected a range, only dates in the range will be selected; otherwise all
dates will be used.
· For each selected date, records in the Cust. Ledger Entry data item that were
posted on that date will be selected.
· The records of the Cust. Ledger Entry data item will be grouped according to
the value of the Document Type field, and totals will be maintained for the
Amount field.
The sections contain some controls that do not have fields from the data items
as source expressions (Qty, TotalQty, TotalAmount and DateFilter). The
purpose of these controls will be explained below.
· There is a Header section for the Date data item, containing captions for the
columns of data in the report.
· There is a Footer section for the Date data item, used for printing totals for
all printed records.
· There are two GroupFooter sections for the Cust. Ledger Entry data item,
one with a text box for the Date field, one without. The reason for this
construction–and how to use it–will be explained below. Both sections will
print summarized information about the groups of this data item (remember
that the Document Type field was used for grouping here).
· There is a Footer section for the Cust. Ledger Entry data item, used for
printing totals.
Neither data item has a Body section.
1 The number of entries must be counted for each document type, for each
date and for the complete range of dates in the report.
2 The total amount for all entries in the report must be calculated.
3 The date for a group of entries (with different document types) should be
printed only once, when the first record in the group is printed.
4 At the end of the report, when the total number of entries and the total
amount are printed, the date range that was selected by the user should be
printed.
However, each record corresponds to exactly one entry. This means that the
number of entries can be counted by simply counting the records. It can be
done in this way
2 Add the following C/AL code to the triggers of the Cust. Ledger Entry data
item:
The CREATETOTALS function will maintain totals for each group and a grand
total for the iteration of the data item loop. As data items are grouped
according to the Document Type field, Qty will contain the sum of all entries
with the same document type each time the Cust. Ledger Entry GroupFooter
section is printed. When the Footer section is printed, Qty will contain the sum
of all entries (that were selected–that is to say: all entries that pertain to the
same date).
1 Declare two global variables: TotalAmount and TotalQty (refer to the picture
on page 219).
2 Add these lines to the OnAfterGetRecord trigger of the Cust. Ledger Entry
data item:
TotalQty := TotalQty + 1;
TotalAmount := TotalAmount + Amount;
The first line simply adds one to the TotalQty variable whenever a record is
retrieved, while the second line adds the retrieved Amount to the TotalAmount.
When the Date data item loop ends and the Footer section is printed, TotalQty
and TotalAmount will contain the wanted values.
One solution is not to print a Body section for the Date data item at all, but to
print the Date field from the Cust. Ledger Entry data item. This creates another
problem, though: if it is added to the GroupFooter section, the date will be
printed on every line. While this could be an easy way to solve the problem, the
finished report will not be very attractive. Besides, it will be difficult to read the
report if it is cluttered with redundant information.
A better solution is to define two GroupFooter sections for the Cust. Ledger
Entry data item–one that includes the Date field and one that does not–and
then control when they are output.
3 Add the following line to the OnPreDataItem trigger of the Cust. Ledger
Entry data item in order to initialize the IsDatePrinted variable before each
iteration of the data item loop:
IsDatePrinted := FALSE;
4 Add the following lines to the OnPreSection trigger of the first GroupFooter
section of the Cust. Ledger Entry section:
IF IsDatePrinted THEN
CurrReport.SHOWOUTPUT(TRUE)
ELSE
CurrReport.SHOWOUTPUT(FALSE);
IF IsDatePrinted THEN
CurrReport.SHOWOUTPUT(FALSE)
ELSE BEGIN
CurrReport.SHOWOUTPUT(TRUE);
IsDatePrinted := TRUE;
END
1 When a new iteration of the Cust. Ledger begins, a date has not yet been
printed.
2 If the loop generates any output at all, only the second GroupFooter section
(containing the Date text box) will be included as output in the first
iteration.
would look good if the final line of this header could display the range of dates
that the user selected. This is easy to implement:
1 Create a variable of type Text, with a length of 100, and call it DateFilter.
3 Add a text box to the footer section of the Date data item that has DateFilter
as source expression.
When the OnPreReport trigger is executed, the RequestForm will already have
been run. The GETFILTER function returns any filters on the field that is passed
as an argument as a text string.
Date Qty
Document Type Amount
Invoice 1 1,906.25
Total 1 10,101.25
Total 1 -6,950.00
Total 1 74,658.58
Total 1 12,162.65
Total 4 408,218.41
Total 1 27,027.30
Invoice 2 149,167.22
Total 4 129,841.97
The Sales Invoice Header table contains general information about each
posted sales invoice, while the Sales Invoice Line table contains the individual
lines that are part of each invoice. The tables are related though a field that is
called No. in the header table (and is the primary key of this table) and
Document No. in the lines table.
2 Create another data item, based on the Sales Invoice Line table, and indent
this data item one level.
4 Enter the Amount field as the value of the TotalFields property of the Sales
Invoice Line data item, in order to calculate the total amount for all lines on
the invoice.
5 Finally, in order to let the users of the report select a posted invoice to print,
enter the No. field as the value of the ReqFilterFields of the Sales Invoice
Header data item.
This completes the definition of the data model itself. In this report, some
supporting variables are needed in order to access information from tables
that cannot be fitted into the data model.
1 Choose C/AL Globals from the View menu. This will open the form where
you can declare variables.
3 The two last variables must be declared as arrays. Open the Property Sheet
for each variable and set Dimensions to 6 for the variable called CustAddr,
and to 4 for the variable called CompAddr.
This concludes the definition of the data model. Next, a small amount of C/AL
code must be added to the report triggers.
The entire code is in the triggers of the first data item, Sales Invoice Header. In
the OnPreDataItem trigger, the statements work like this
· The first six lines assign values from the record in the Sales Invoice Line
data item to elements of the CustAddr array.
· After this, COMPRESSARRAY is used for the reasons described above.
· The last two lines use the GET function (with the codes for Payment Terms
and Shipment Method from the Sales Invoice Header record as arguments)
to retrieve the related records from the Payment Terms and Shipment
Method tables. When you design the sections, the full text descriptions can
then be extracted from these records.
In the Header section of the Sales Invoice Header data item, you should notice
these points:
This is how the invoice document looks when sample data is used:
Invoice
No. 943028
Total 12,162.65
A Nonprinting Report
To complete the examples, we will create a nonprinting report. Although you
can achieve the same functionality by writing a codeunit, there are several
good reasons for using nonprinting reports whenever you can:
3 Set the value of the DataItemTableView property of the Item data item to
No. (by using the assist-edit button). Though this is not strictly necessary
for the functionality of the report, it does serve one purpose: it removes the
Sort... button from the request form that will be presented to the user of the
report. As the report will not print anything, the order in which data items
will be run through is irrelevant.
4 Select the fields that the user will be able to filter, by using the assist-edit
button in the ReqFilterFields property:
· The Window variable, declared as a Dialog type, will be used for printing a
message on the screen while the report runs.
· The Adjustment variable will be used for the value that the user enters in the
request form.
· NewPrice will be used to store an intermediate result.
1 Open the Request Options Form Designer by choosing Request Form from
the View menu.
2 Add a text box with a label to the form (to have the label added
automatically, press the Add Label button in the Toolbox before selecting
the Text Box tool).
3 In the Property Sheet of the text box, set the source expression to
Adjustment, the newly created variable.
· The first statement in the OnAfterGetRecord trigger enters the item number
in the window each time a new record has been retrieved.
· The second statement in the OnPreDataItem simply causes the report to
end without doing any processing if the adjustment factor is 0 (zero). If the
adjustment factor were allowed to be zero, then all prices in the table would
be set to zero, which would certainly never be the intention. The statement
used here is a very crude way of handling this situation: in a more polished
version, the user should, for example, have an opportunity to reenter the
adjustment factor (or at least be notified of the reason for quitting the
report run).
· The last three lines in the OnAfterGetRecord trigger actually update the
prices. First, the adjusted value is assigned to the NewPrice variable. Then,
the VALIDATE function of the Unit Price field is used to update the price. In
this way, any special processing (for example, updating of other fields that
are related to this field) in the OnValidate trigger of the table field will be
performed. Finally, the MODIFY function is used to commit the change.
Global variables A global variable is a variable whose scope covers all the
functions in the codeunit.
Each function you add to a codeunit will be shown in a separate section when
you view the file in the C/AL editor.
All codeunits include two default sections called Documentation and OnRun. In
the Documentation section, you can add optional information about the code
such as the purpose of the codeunit, a version number and so on. In the OnRun
section, you can include code that you want the system execute when the
codeunit is run.
To create a codeunit:
1 From the menu bar, choose Tools, Object Designer. C/SIDE will open the
Object Designer:
Select codeunit
2 Click New to create a new codeunit. C/SIDE will open the C/AL editor, where
you can create functions.
3 Click the Design button. C/SIDE will open the C/AL editor, where you can
modify the codeunit by changing existing functions or adding new functions.
When you create a codeunit, the window shows the two default sections
described above (the Documentation and the OnRun section).
From the Object Designer you can open as many codeunits as you like. Each
time you create a new codeunit or open an existing one, it will be displayed in a
separate window. This makes it easy to cut and paste lines of code between
the codeunits.
If you have tried to use other Windows editors, you’ll find the C/AL editor easy
to use. You can access the editing functions both from the Edit menu and from
the toolbar.
Paste
Cut Copy
When you are working in the C/AL editor, you can use a number of shortcut
keys:
To... Press...
cut the selected text to the clipboard Ctrl+x
copy the selected text to clipboard Ctrl+c
paste the text at the clipboard into the codeunit at the Ctrl+v
cursor position
Make sure that focus is on the C/AL editor. From the View menu, choose
C/AL Globals. C/SIDE will display the C/AL Globals window:
In the C/AL Globals window, you select whether you want to add a global
variable or a function.
2 Add a name and a type. If the type you select corresponds to a application
object, you also have to add a subtype, that is, the name of a specific object
in the database. If you select text or code you have to define a length for the
variable (the default length is 10 characters for code, and 30 for text). Refer
to Defining and Using a Temporary Table on page 69 for information about
how to create temporary tables.
To add a function:
1 Click the Functions tab in the C/AL Globals window. C/SIDE will display:
3 Press the Locals button to define the parameters, return value and local
variables for each function. C/SIDE will display the C/AL Locals window:
4 For each parameter you have to specify the calling method, a name, and a
data type. You can specify a subtype and a length, but this is optional.
The calling method can be specified as Var, which means that the parameter
is passed by reference rather than by value. The value of a variable can only
be changed by a function when it is passed to the function by reference.
When the parameter is not specified as Var, only a copy of the variable is
passed to the function. If the function changes that value, the change
affects only the copy and not the variable itself.
If the type you select corresponds to an application object, you also have to
add a subtype, that is, the name of a specific object in the database. If you
select text or code you have to define a length for it (the default length is 10
characters for code, and 30 for text).
5 Click the Return Value tab to define the return value for your new function.
C/SIDE will display:
6 Enter a name for the return value and select a data type from the drop-down
list. You can also select a length, but only if the type is text or code.
7 Click the Variables tab in order to define local variables. C/SIDE will display:
8 For each local variable you must add a name and a type. If the type you
select corresponds to an application object, you also have to add a subtype,
that is, the name of a specific object in the database. If you select text or
code, you have to define a length for the variable (the default length is 10
characters for code, and 30 for text).
If you use the OK button, the Symbol Menu window is closed automatically; it
stays open if you use the Apply button.
If you need help about any of the C/AL functions shown in the column to the
right, put the focus on the function name and press F1 to activate the context-
sensitive online C/SIDE Reference Guide.
2 If the system finds any errors in your code it will display a message. Correct
the errors and choose Compile from the Tools menu again.
<CodeunitName>.<FunctionName>
Example
Assume that you have created a codeunit containing two statistical functions named F and G. The
illustration below shows how to access these functions from a form.
Any form
...
Result := StatFun.F(3425)+StatFun.G(346);
...
This method is generally applicable. That is, from any application object you
can access functions in other application objects by prefixing the function
name with the name of the application object containing the function.
Limitations on Codeunits
Global variables and temporary tables in a codeunit cannot be accessed
directly from other application objects. The only way to access these values is
through the functions you have created in the codeunit.
All C/AL functions can be used in a codeunit. Notice, however, that you cannot
create a function with the same name as a built-in function. Neither can two or
more user-defined functions have the same name (unless they are part of
different application objects).
Here are the most important things C/AL lets you do:
Design Your Own Functions Although C/SIDE has a large number of intrinsic
functions, you will sometimes find it convenient or necessary to add your own
functions–for example if the application you are developing repeatedly uses
the same non-trivial processing.
Connect Database Objects C/AL code glues your database objects together.
C/AL includes a number of commands that control how the individual database
objects in your application interact.
Read, Write and Modify Data C/AL includes standard functions for reading,
writing and modifying table data.
· Statements
· Expressions
· Data types
· Operators
Consider the following C/AL code sample:
Amount := 34 + Total;
This individual code line is also called a statement. The table below illustrates
how the statement can be can broken into smaller elements.
Element Description
34 + Total An expression. In this case the expression consists of an arithmetic operator
(+) and two arguments (34 and Total), which also could be called sub-
expressions. All valid C/AL expressions can be evaluated to a specific value.
:= The assignment operator. When the right-hand side expression has been
evaluated, this operator is used to assign (store) the value in the variable
Amount.
Amount This is called a variable. It is used to reference a memory location where data
is stored.
This function takes three simple expressions as arguments, 31, 12 and 1996.
· Constants
· Variables
· Operators
· Functions
Depending on the elements in the expression, the evaluation will result in a
value with a C/AL data type. The table below shows some typical expressions.
The above examples show that when C/AL expressions are evaluated, the
results have a specific data type. The next section explains the C/AL data types
in more detail.
needed in your calculations. For example, if you try to store the value 1233.345
in an integer variable you will get a run-time error. C/AL contains a wide range
of data types. These data types can be divided into the following categories:
Fundamental option
integer Numeric
decimal
char
text
String
code
date
time
boolean
binary
Complex BLOB
table
form
codeunit
file
system
dialog
report
option This denotes an option value. Option values can freely be converted to
numeric ones. The values range from -2,147,483,647 to 2,147,483,647.
Example
Assume that Number is a numeric variable and that Type denotes a field of type Option in the
Purchase Header table. In the statement below the option value is converted to a number:
Example
This example illustrates how the possible values of an option field can be used as constants in your
C/AL code:
date Denotes dates ranging from January 1, 0 (the year zero) to December 31,
9999. An undefined date is expressed as 0D. All dates have a corresponding
closing date. The closing date for a given date is regarded by the system as a
period following the given date but before the next normal date. Thus a closing
date is sorted immediately after the corresponding normal date but before the
next normal date.
time Denotes a time. An undefined time is expressed as 0T. Any time in the
range 00:00:00 to 23:59:59.999 is valid.
char Stores a single character as a value in the range 0 to 255. This data type
can be freely converted between a number and a character. This means that
you can use the same mathematical operators as with a number type variable.
Example
C := "A";
Example
You can also assign a single char in a text, code or binary type variable to a char variable:
C := S[2];
A[65] refers to the 65th character in the variable called A. The resulting values
will be of type char. The length of a variable of type text corresponds to the
number of characters in the text. An empty text string thus has the length 0.
The table below illustrates some typical examples of text strings. In these
examples it is assumed that the variable t is of type text and has a maximum
length of 6.
Example
The table below shows some typical examples of code string assignments. In the examples, it is
assumed that the variable c has the type code, and the maximum length 4.
BLOB This is a Binary Large Object. Variables of this data type differ from
normal numeric and string data type variables in that they have a variable
length. BLOBs are used to store memos (text), bitmaps (pictures) or user-
defined types. The maximum size of a BLOB is normally determined by your
systems disk storage capacity, as the upper limit is 2GB.
form Variables of this data type are used to store forms. This is a complex
data type which can contain a number of simpler elements called controls.
Controls are used to display information to the user or to receive user input.
codeunit Variables of this data type are used to store codeunits. This is a
complex data type which can contain a number of user-defined functions.
file Variables of this data type give you access to operating system files.
dialog Variables of this type are used to store dialog windows. A number of
functions are available for manipulating dialogs.
report Variables of this data type are used to store reports. This is a complex
data type that can contain a number of simpler elements called controls.
Controls are used to display information to the user.
dimension can contain but an array variable can never have more than
1,000,000 elements in all. The physical size of an array is limited to 2 GB (or
available memory). Arrays are always indexed with a number for each
dimension that ranges from 1 to (and including) the size of the dimension. If
you accidently index outside the range of the dimensions of an array, a run-
time error will occur.
Example
Assume that Foo is a one-dimensional array variable of the Integer type, with the dimension 10.
To index the first element, use Foo[1]. To index the last element, use Foo[10].
Example
Assume that Bar is an array variable of type Date with the dimensions 2x3x4. Then Bar has 24
elements.
To index the first element, use Bar[1,1,1]. To index the last element, use Bar[2,3,4].
· Constants
· Variables
· Operators
· Functions
We start by defining ranges and properties of constants in C/AL.
Constants
A constant is the simplest type of operand used in C/AL. The value of a
constant cannot be changed during the execution of the code. Constants can
be defined for each of the simple data types in C/AL.
decimal constant A decimal constant must contain a decimal point '.' and
have at least one digit to the right of the decimal point (for example the digit
'0'). A constant of type decimal can be used to represent decimal numbers
between -999,999,999,999,999.99 and 999,999,999,999,999.99 with 18
significant digits.
date constant A date constant is written as six or eight digits followed by the
letter 'D' (the date constant expressing 'undefined date' is, however, entered
as '0D'). The digits specify the date in the format MMDDYY or MMDDYYYY.
time constant A time constant is written as six or nine digits followed by the
letter 'T' (the 'undefined time' constant is, however, entered as '0T'). The nine
digits specify the time in the format HHMMSS[.XXX], that is, a 24 hour format
with an optional part specifying thousandths of a second.
Constant Description
TRUE boolean constant
50000 integer constant
-23.7 decimal constant
122196D date constant (December 21, 1996)
141230T time constant (the time 14:12:30)
'ABC’ string constant
User-defined variables are ones you define when you create new C/AL code.
You can define variables that are global to all functions within a codeunit and
variables that are local to each function in a codeunit. Both types of user-
defined variables are local to the codeunit in which they are defined. These
variables can be used to store information at runtime, and the values can be
changed as desired.
When the system is running, it executes code in functions and triggers, for
example entry-processing code for a table. Before the code is executed, the
system automatically assigns values to the associated system-defined
variables, and in the triggers and the local functions the values of these
variables can be used.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
the value in a system-defined variable does not propagate backwards. In other
words the user cannot use a system-defined variable to modify the state of the
system.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Variable Names
Variable names must be unique, that is, two user-defined variables with the
same name are not allowed in a C/AL codeunit. Furthermore, you cannot have
user-defined and system-defined variables with the same name. Uppercase
and lowercase letters are not distinct, that is Smith and SMITH refer to the
same variable. In standard Pascal notation, a variable name (an identifier) can
only be written as an unbroken word. This restriction is relaxed in C/AL: here it
is also possible to use special characters (for example spaces) in a variable
name.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
the double quotes are not part of the variable name, but are necessary in order
to avoid a compile-time error.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Here are a number of examples showing valid variable names:
· Customer
· StockGroup1
· "@Vendor"
· "1st AddressLine"
· "Purchase/Sales"
· "Sales In GBP"
· " YesCrazy Name1Ñ3"
...and the following are examples of invalid variable names
· 34467
· 23"Tubes
· Stock Group4
· "Sale"s in GBP"
· )-Names
· END
Initialization
Variables are automatically initialized before C/AL code is executed. A boolean
variable is set to FALSE and numeric variables are set to the default value zero,
while strings (text and code) are initialized to the value '' (the empty string)
and date and time variables are set to the undefined time 0T and the undefined
date 0D, respectively.
· A parameter in a function call does not have the correct type. This occurs for
instance if a function that is supposed to be called with an integer argument
is called with, for example, a decimal argument.
· The evaluation of the expression on the right-hand side of an assignment
operator (:=) results in a type that differs from the type of the variable on
the left-hand side.
The automatic type conversion in assignments can freely take place between
the following numeric data types, provided overflow does not occur:
The automatic type conversion in assignments can also freely take place
between the String data types:
code text
All the above has been based on simple variables. Nevertheless, the same
assignment rules apply for arrays in C/AL. Furthermore, if the left operand in an
assignment (the variable) is an array, the dimension(s) of the right-hand
expression must correspond to the dimension(s) of the variable.
Notice That...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
the type conversion that takes place in assignments can cause run-time errors
even though the types are convertible. A run-time error can occur in an
assignment if the converted value is outside the valid range for the left-hand
side variable. Correspondingly a run-time error can occur if the converted value
is outside the valid range for a parameter in a function call.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Example
Let the variable A be defined as a one-dimensional array with four text type elements with the
maximum length 15. A value could be assigned to the second element in the array as shown below:
Example
Result is an option variable, while Amount and Total both are decimal variables.
Consider the following assignment statements:
Amount := 10;
Total := 4;
...
Result := Amount + Total;
The above code can always be compiled, but a run-time error will occur if the result of the right-hand
side expression 'Amount + Total' exceeds the valid range of the datatype of the left-hand side
variable Result, that is, outside the range -2,147,483,647 to 2,147,483,647.
Valid Assignments
The table below shows whether it is possible to assign the value of an
expression of a given type to a variable of a given type:
Expression Type
Variable boolean char option Integer decimal date time text code
Type
boolean c j j j j j j j j
char j c i i i j j j j
option j c c c i j j j j
integer j c c c i j j j j
decimal j c c c c j j j j
date j j j j j c j j j
time j j j j j j c j j
text j j j j j j j i i
code j j j j j j j i i
The '+' and the '-' operators can be used both as unary and binary operators,
the 'NOT' operator only as an unary operator, while all other operators are
binary.
Most of the above operators can be used on different data types. The action of
these operators may depend upon the data type of expression they are used
on. Below are some typical examples
Example
number + number
This returns the sum of the numbers, that is, a result of the type number.
Example
string + string
This returns the concatenation of the strings, that is, a result of the type string.
Example
The '+' operator can be used as an unary operator to indicate sign, for instance:
+ 34545
You can read more about the function of each operator in the appendix Type
Conversion, on page 327., which explains the type conversion mechanisms in
C/AL.
Operator Hierarchy
The operators just discussed are organized in a hierarchy that determines the
order in which the operands in a given expression will be evaluated. The list
below shows the precedence order of the C/AL operators:
4 +, -, OR
6 .. (range)
The example below illustrates the effect of the operator hierarchy. The
expressions, which apparently are the same, will produce different results.
Example
The expression
2 + 3 * 4
(2 + 3) * 4
is evaluated to 20.
Function Calls
C/AL has a number of functions for different purposes, such as string handling,
text formatting, database handling and so on. Some of these functions differ
from standard Pascal, as it is possible to use a variable number of parameters.
In a function call, the parameters are separated by commas, and the optional
parameters may be omitted from the right.
This means that if the function has, for instance, 3 optional parameters, then it
is not possible to omit the second without omitting the third.
Example
can be called as
FUNCTION(Optional1, Optional2)
but not as
Example
ABS is a typical example of an C/AL function with a fixed number of parameters (1).
Example
The function DMY2DATE is a typical example of a function which can be called with a variable
number of parameters
Depending on the use of the DMY2DATE function, 1, 2 or 3 parameters can be passed to the
function, as the second and third parameter are optional. When the second and third parameters
are not used, the system uses values from the system date as default.
The control structures in C/AL are divided into the following main groups:
· Compound Statements
· Conditional Statements
· Repetitive Statements
· WITH Statements
BEGIN
<Statement 1>;
<Statement 2>;
.
.
<Statement n>;
END
The above BEGIN END structure is also called a block. Blocks can be very useful
in connection with the other control structures to be discussed in the following.
Conditional Statements
By using a conditional statement, you can specify a condition and one or more
commands to be executed, according to the evaluation of the condition: TRUE
or FALSE. There are two types of conditional statements in C/AL:
which means
As defined earlier, the square brackets around ELSE <Statement2> means that
this part of the statement is optional.
Several nested IF THEN ELSE statements may seem confusing but a general
rule is that an ELSE belongs to the last IF that lacks an ELSE.
Example
Example
(1)...
(2) IF Amount < 1000
(3) THEN BEGIN
(4) IF I > J THEN Max := I
(5) ELSE Max := J;
(6) Amount := Amount * Max;
(6) END
(7) ELSE
(8)...
A common error for the C/AL newcomer is to put an extraneous semicolon at the end of a line before
an ELSE (line 4). As mentioned above, this is not valid according to the syntax of C/AL, as the
semicolon is used as a statement separator. (The end of line 4 is inside the inner IF statement).
CASE <Expression> OF
<Value set 1> : <Statement 1>;
<Value set 2> : <Statement 2>;
...
...
<Value set n> : <Statement n>;
[ELSE <Statement n+1>]
END;
In the above definition, the <Expression> cannot be a record, and the <Value
set> must be an expression or a range.
CASE statements are also called multiple option statements and are typically
used when a selection between more than two different actions is to be made.
The function of the CASE statement is as follows:
· The <Expression> is evaluated, and the first matching value set causes the
associated statement, if any, to be executed.
· If none of the value sets matches the value of the expression, and the ELSE
part has been omitted, no action will be taken; but if the optional ELSE part
is used, then the associated statement will be executed.
The type of the value sets must be the same as the type of <Expression> or at
least convertible to the same type.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
the data type of the value sets will be converted to the same data type as the
evaluated <Expression>, if necessary. This type conversion may cause an
overflow at run time if the resulting data type cannot hold the values of the
value sets.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Example
This C/AL code sample will print various messages depending on the value of Number. If the value
of Number does not match any of the entries in the CASE structure, the ELSE entry will be used as
default.
CASE Number OF
1,2,9: MESSAGE('1, 2 or 9.');
10..100: MESSAGE('In the range from 10 to 100.');
ELSE MESSAGE('Neither 1, 2, 9, nor in the range from 10 to 100.');
END
· FOR, which repeats the inner statement until a counter variable equals the
maximum or minimum value specified.
· WHILE, which repeats the inner statement while the specified condition is
TRUE. The statement in a loop of this type is repeated 0 or more times.
· REPEAT, which repeats the inner statements until the specified conditions
evaluate to TRUE. The statements in a loop of this type are always executed
at least once.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
if the value of the control variable is changed inside the FOR loop, the behavior
of the system is not predictable. Furthermore, the value of the control variable
is undefined outside the scope of the FOR loop.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Example
The following FOR loop uses an integer control variable named Count.
When the above statement is executed, a run-time error will occur because the system tries to
convert the start and end values to the same type as the control variable; but as Count has been
declared as an Integer variable, an error will occur when 10000000000000000 is to be
converted, because this end value is outside the valid range for Integers.
Example
FOR I := 1 TO 5 DO
FOR J := 1 TO 7 DO
A[I,J] := 23;
The two FOR statements above could be used to initialize every element in a 5 x 7 array with the
value 23.
Example
The C/AL code below increases the variable i until it equals 1000:
This might at first glance seem to function just like a WHILE control structure,
but as the REPEAT UNTIL statement is executed from left to right, it is easily
seen that the <Statements> always will be executed at least once, no matter
what the <Condition> is evaluated to. This contrasts with the WHILE control
structure, which performs the evaluation before the <Statement> is
executed–implying that if the first evaluation of <Condition> returns FALSE,
then no statements will be executed.
Example
Count := 0;
IF Customer.FIND(`-') THEN
REPEAT
Count := Count + 1;
UNTIL Customer.NEXT <= 0;
This code uses a REPEAT UNTIL loop to count the number of entries in the Customer table. The FIND
function finds the first entry in the table. Each time NEXT is called, it steps one record forward. When
NEXT = 0 there are no more entries in the table and the system exits the loop.
EXIT([<Value>])
A compile-time error will occur if EXIT is called with a return parameter from:
· system-defined triggers.
· local functions that are not supposed to return a value.
Example
The following illustrates the use of the EXIT statement in an arbitrary local function. Assume that the
IF statement is used to detect an error. If the error condition is met, the execution is stopped and the
local function returns the error-code 1.
FOR I := 1 TO 1000 DO
BEGIN
IF Amount[I] < Total[I] THEN EXIT(1);
A[I] := Amount[I] + Total[I];
END;
When you work with records, addressing is carried out as record name, dot
(period) and field name: <Record>.<Field>
If you work continuously with the same record, you can use WITH statements.
When you use a WITH statement, it is only necessary to specify the record
name once.
Several nested WITH statements may be used. In case of identical names, the
inner WITH will overrule the outer WITH-statements.
Example
CustomerRec.No := '1234';
CustomerRec.Company := 'Windy City Solutions';
CustomerRec.Manager := 'Joe Blow';
CustomerRec.Address := '1241 East Druid Avenue';
CustomerRec."State and Zip":= 'Chicago, IL 60079';
WITH CustomerRec DO
BEGIN
No := '1234';
Company := 'Windy City Solutions';
Manager := 'Joe Blow';
Address := '1241 East Druid Avenue';
"State and Zip" := 'Chicago, IL 60079';
END;
· Use '//' to insert a single line comment. When the compiler encounters the
'//' symbol in your code, it interprets the rest of the line as a comment.
· Use '{' and '}' to mark the beginning and end, respectively, of a block of
comments.
Any number of nested comments may occur. In such cases, the comment runs
from the first comment start to the last comment end.
Example
{
This is a sample comment which is ignored by the C/AL compiler
}
Example
Example
Example
A := 34;
B := 56; {******************
C := 345; * Don’t do this! *
D := 781; ******************}
Because the comment is to the right of the C/AL statements, the system assumes that the third and
fourth lines are part of the comment. That is, only A and B are assigned values, while C and D are
not. Instead you should use single line comments:
A := 34;
B := 56; //*******************
C := 345; //* Do it this way! *
D := 781; //*******************
· System-Defined Variables
· Handling Run-Time Errors
· The Essential C/AL Functions
Chapter 14. Using C/AL
14.1 Overview
This chapter describes how to use C/AL. The first sections concentrate on
giving some advice on the things you should consider when you use C/AL–the
most important subject being where you place the code.
· Tables
· Table fields
· Forms, including request options forms
· Form controls
· Reports
· Data items
· Sections
The execution of C/AL can also be initiated from:
· Command buttons
· Menu items
You can also place C/AL code in codeunits and call it from code in any of the
locations mentioned above.
As you can see, you can put C/AL code in a large number of places and initiate
or trigger its execution in many ways. You should not, however, choose a
location for your C/AL code at random. A few simple guidelines should be
followed:
· In general, place the code as close as possible to the object it operates on.
This implies that code that modifies records in the database should
normally be placed in triggers of the table fields that are involved.
278 Overview
Chapter 14. Using C/AL
Reusing Code
Perhaps the most important reason for placing C/AL code consistently, and as
close to the objects it manipulates as possible, is that it lets you reuse code.
Reusing code make it faster and easier to develop applications, but this alone
is not the most important reason for reusing code whenever you can. If you
place your C/AL code as suggested, your applications will be less prone to
errors.
Overview 279
Chapter 14. Using C/AL
Variable Comments
Rec When a record is modified, the Rec variable contains the current record
xRec (including the changes that are made), while the xRec variable contains the
original values (before the changes).
CurrForm Refers to the current form. You can access the controls of the form through
this variable and set the dynamic properties of the form and its controls.
CurrReport Refers to the current report in the same way as CurrForm refers to the
current form.
RequestOptionsForm Refers to the request options form of the current report.
CurrFieldNo The field number of the current field in the current form–retained for
compatibility reasons.
In addition, some triggers (for example, the OnFormat trigger of a control) have
a parameter that is defined as a local variable by the system.
Example
You could put the Rec/xRec pair of records to use in a situation like this: in an application, data is
stored in two tables, a header table and a lines table. The header table contains general information
about, for example, sales orders, while the lines table contains the specific order lines. On the form
where the user enters information in the header table there are fields that contain the customer’s
address. These fields are related to a Customer table, and can be filled out by using a lookup
function in the field that establishes the relationship. In the header table, only the customer number
is stored, and the other fields with customer information (name, address, and so forth) are retrieved
from the Customer table when the Customer No. field is validated.
Now, should the user be able to change the customer number? In some situations the answer would
be yes, in others no. If the order has already been shipped, the answer should definitely be no, but
there could be situations where it would be yes–it should, for example, be possible to correct an
erroneous number on an order that has not been processed any further.
- When validating the customer number field, check whether the order has been shipped.
- If it has, compare the customer number fields in the xRec and Rec records. If they differ, reject the
change.
In real life, you would certainly add some more checks and some user dialog, but this is the basic
idea.
When you use these functions, four different scenarios are possible, as
depicted in this table
Ok is a boolean value, which will be TRUE if the record is found and FALSE
otherwise. If GET is used as below, and no record is found,
Customer.GET("Customer Number");
a run-time error will occur. If, on the other hand, GET is used as below, and no
record is found,
execution will continue. In this case you will need to handle the situation
yourself in the ELSE part of the statement.
Below are some examples of how to use this set of essential functions. You
should, however, always refer to the online C/SIDE Reference Guide for full and
updated information on any C/AL function.
GET GET retrieves one record, based on the value of the primary key. That is, if the
No. field is the primary key of the Customer table, GET can be used like this:
GET(Customer,'4711');
The result will be that the record of customer 4711 will be retrieved. GET is one
of those functions that will produce a run-time error if it fails and the return
value is not inspected by the code, and otherwise not. This means that your
actual code would probably look more like this:
IF GET(Customer,'4711') THEN
.... // do some processing
ELSE
.... // do some error processing
GET searches for records, regardless of current filters, and it does not change
any filters. In other words: GET always searches among all records in a table.
FIND An important difference between GET and FIND is that FIND respects (and is
limited by) the current setting of filters. Further differences are that FIND can
be instructed to look for records where the key value is equal to, larger than or
smaller than the search string, and finally, FIND can find the first or the last
record (given the sorting order defined by the current key).
You can use these features in various ways. When developing applications
under a relational database management system, you will often have one-to-
many relationships between tables. An example could be the relations
between an Item table, which registers items, and a Sales Lines table, which
registers the detail lines from sales orders. Obviously, one record in the Sales
Line table can only be related to one item, but each item can be related to any
number of sales line records.
You would not want an Item record deleted while there are still open sales
order records that include the item. You can use FIND to check this.
To do this, insert the following code in the OnDelete trigger of the Item table:
SalesOrderLine.SETCURRENTKEY("Document Type",Type,"No.");
SalesOrderLine.SETRANGE(
"Document Type",SalesOrderLine."Document Type"::Order);
SalesOrderLine.SETRANGE(Type,SalesOrderLine.Type::Item);
SalesOrderLine.SETRANGE("No.","No.");
IF SalesOrderLine.FIND('-') THEN
ERROR(
'You cannot delete because there are one or more outstanding
salesorders that include this item.');
NEXT NEXT is often used with FIND to step through records of a table, as in this
fragment:
FIND('-');
REPEAT
// process record
UNTIL NEXT = 0;
Here, FIND is used to go to the first record of table. Afterwards, NEXT is used to
step through every record, until there are no more (then, NEXT returns 0
(zero)).
SETCURRENTKEY This function is used to select a key for a record, thereby setting the sorting
order that will be used for the associated table. SETCURRENTKEY has this
syntax:
You should have these points in mind when you use SETCURRENTKEY:
2 When searching for a key, C/SIDE selects the first occurrence of the
specified field(s).
For example, even if you specify only one field as a parameter when calling
SETCURRENTKEY, the key that is actually selected may consist of more fields; if
several keys have as their first component the field that you specified, you may
not get the key that you think you will.
If no keys can be found that include the specified field(s), a run-time error will
occur unless you test the boolean return value of SETCURRENTKEY in your
code. If you do test the return value, you will have to decide what to have the
program do if the function returns FALSE, because without a run-time error, the
program will continue to run even though no key has been found.
SETRANGE This function is used to set a delimitation on a field–that is, a simple filter. The
syntax is:
as in this example:
Customer.SETRANGE("No.",'10000','90000');
which would limit the Customer table by selecting only those records where
the No. field has a value between 10000 and 90000.
already be set. And, finally, if only From-Values is used, To-Value will be set to
the same value as From-Value.
SETFILTER SETFILTER sets a filter in a more general way than SETRANGE. SETFILTER has
this syntax:
where Field is the name of the field to set a delimitation on. String is a filter
expression that may contain %1, %2 and so on to indicate locations where the
system will insert values (but not operators) given as the Value parameter(s) in
a filter expression.
This statement would select records where the No. is larger than 10000 and not
equal to 20000.
Customer.SETFILTER("No.",'>%1&<>%2',Value1, Value2);
GETRANGEMIN This function retrieves the minimum value of the delimitation currently in effect
for a field. GETRANGEMIN has this syntax:
Record.GETRANGEMIN(Field);
GETRANGEMIN will cause a run-time error if the filter currently in effect is not a
range. That is, if a filter has been set like this:
Customer.SETFILTER("No.",'10000|20000|30000');
then
BottomValue := Customer.GETRANGEMIN("No.");
Generally, these functions return a boolean value that indicates whether they
succeed. If you do not handle the return value in your code, a run-time error
will occur when a function returns FALSE. If you handle the return value–by
testing its value in an IF statement–no error will occur, and you must take
corrective action yourself (knowing that the function did not succeed, of
course).
Customer.INIT;
Customer."No." := '4711';
Customer.Name := 'John Doe';
Customer.INSERT;
These statement will insert a new record, with No. and Name having the
assigned values, while other fields will have their default values. Supposing
that No. is the primary key of the Customer table, the record will be inserted in
the Customer table unless there already is a record in the table with the same
primary key. In that case, as the return value is not tested, this error message
would be displayed:
Customer.GET('4711');
Customer.Name := 'Richard Roe';
Customer.MODIFY;
The statements above would change the name of customer 4711 to Richard
Roe.
MODIFYALL This function is used to do a bulk update of records. MODIFYALL respects the
current filters, meaning that you can perform the update on a specified set of
records within a table. MODIFYALL does not return any value, nor does it cause
an error if the set of records to be changed is empty.
Customer.SETRANGE("Salesperson Code",'PS','PS');
Customer.MODIFYALL("Salesperson Code",'JR');
The SETRANGE statement selects the records where Salesperson Code is PS,
and MODIFYALL changes these records to have Salesperson Code set to JR.
DELETE This function is used to delete a record from the database. The record to delete
must be specified (using the value(s) in the primary key fields) before calling
the function. (This means that DELETE does take filters into consideration.)
Here is an example in which DELETE is used to delete the record with customer
number 4711:
Customer."No." := '4711';
Customer.DELETE;
DELETE returns a boolean value: TRUE if the record could be found, FALSE
otherwise. Unless you test this value yourself, a run-time error will occur when
DELETE fails (returns FALSE).
Now, this can cause problems if, in a multiuser environment, another user
modifies or deletes the record between steps 2 and 3. If the record is modified,
then perhaps the new contents of the record would have changed your
decision to delete it. If it has been deleted by the other user, you can get a
seemingly inexplicable run-time error if you have just verified that the record
existed (in step 1).
If the design of your application indicates that you can encounter this problem,
you should consider using the LOCKTABLE function (described below)–but
DELETEALL This function is used to delete all records that are selected by the filter
settings–if no filters are set, all records in the table will be deleted.
The following statements would delete all records where Salesperson Code is
PS from the Customer table:
Transactions
Normally, you do not need to be concerned with transactions and table locking
when developing applications in C/SIDE–Appendix D, C/SIDE in Multiuser
Environments, explains the details.
There are, however, some situations where you will have to lock a table
explicitly. For example, if you, in the beginning of a function, inspect data in a
table, then use this data to perform various checks and calculations and finally
want to write back a record, based upon the result of this processing, you will
want the values that you retrieved at the beginning to be consistent with the
data in the table now–in short: you cannot allow other users to update the
table while your function is busy doing its calculations.
LOCKTABLE The solution is to lock the table yourself, at the beginning of your function, by
using the LOCKTABLE function.
When you use FlowFields in C/AL functions, you have to update them yourself,
and this is what you use the CALCFIELDS function for. In the statements below,
SETRANGE("Date Filter",0D,TODAY);
CALCFIELDS(Balance,"Balance Due");
CALCSUMS The CALCSUMS function is used to calculate the sum of one or more fields that
are SumIndexFields in the record. For CALCSUMS to work, a key that contains
the SumIndexFields must be selected as the current key. Like CALCFIELDS,
CALCSUMS takes the current filter settings into account when performing the
calculation.
In the statements below, an appropriate key is selected. Then filters are set,
and finally the summation is performed.
SETCURRENTKEY("Customer No.")
SETRANGE("Customer No.",'10000','50000');
SETRANGE(Date,0D,TODAY);
CALCSUMS(Amount);
FIELDERROR The FIELDERROR function triggers a run-time error after having displayed a
field-related error message. The function is very similar to the ERROR function,
described on page 295, but is has some benefits. For one thing, it is easier to
use. The more important reason, however, is that if the name of a field is
changed (for example translated to another language) in the Table Designer,
the message from the FIELDERROR function will reflect the current name of the
field.
Item.GET('70000');
IF Class <> 'HARDWARE' THEN
FIELDERROR(Class);
A message like this will appear when a field has a ‘wrong’ value:
You will see a message like this when a text or code field contains the empty
string:
Finally, you can add your own text if the default texts don’t suit your
application. Then, you call FIELDERROR like this:
FIELDNAME The FIELDNAME function returns the name of a field. Again, you could simply
use the name, as you probably know it when you are writing the code, but by
using FIELDNAME, you can create messages that will still be meaningful if the
FIELDERROR(
Quantity,'must not be less than ' +
FIELDNAME("Quantity Shipped"));
INIT The INIT function initializes a record. If a default value for a field has been
defined (by the InitValue property), this value will be used for the
initialization–otherwise, there is a default value for each data type (see the
online C/SIDE Reference Guide entry for INIT).
Note that INIT does not initialize the fields of the primary key.
TESTFIELD This function is used to test a field against a value. If the test fails, that is if the
values are not the same, an error message is displayed, and a run-time error is
triggered, meaning that any changes made to the record will be discarded. If
the value to test against is the empty string, the field has to have a value other
than blank or 0 (zero).
Code := 'DK'
TESTFIELD(Code,'ZX');
VALIDATE The VALIDATE function is used to call the OnValidate trigger of a field, as in this
example, where it will call the OnValidate trigger of the Total Amount field:
VALIDATE("Total Amount");
Instead, you should perform the calculation in the OnValidate trigger in only
one of the fields and call this trigger code from the OnValidate triggers of the
other fields.
Using a dialog window will also give the user an opportunity to stop the
processing–a Cancel button is automatically part of a dialog window.
The idea is that each field is updated while the program is running. In the
example here, the fields are used to count the number of postings being made.
In another situation you could display, for example, the number of the account
that is currently being processed, like this
2 Open the dialog window, and define the string that will be displayed:
The part of the string that contains pound signs (#) and a number defines a
field that will be displayed in the window, and the number (‘1’ in this
example) can be used to refer to the field.
3 You can display the value of any variable in the field. In the example below,
the number of each account will be displayed as it is processed:
REPEAT
ProgressWindow.UPDATE(1,ChartOfAcc."No.");
// process the account...
UNTIL ChartOfAcc.NEXT = 0;
ProgressWindow.CLOSE;
MESSAGE The MESSAGE function displays a message in a window that remains open
until the user clicks the OK button on the window. Note that MESSAGE
executes asynchronously, that is: MESSAGE is not executed until the function
from which it was called ends or another function requests user input. The
function is useful for notifying the user that some processing has been
successfully completed, as in this example:
MESSAGE(
'Quote %1 has been changed to order %2.',
"No.",SalesOrderHeader."No.");
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
unlike in the example of the progress indication windows, the MESSAGE
function was used without first declaring a variable of type Dialog, since there
will be no need to refer to this window again.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ERROR The ERROR function is very similar to the MESSAGE function, except for one
detail: when the user has acknowledged the message, execution ends. See
also the description of FIELDERROR on page 289.
CONFIRM The CONFIRM function is used is display a message, just like MESSAGE: but
unlike MESSAGE, this function returns a value that can (and must) be used,
depending on whether the user chooses Yes or No. Its obvious use is for asking
a question like this:
The FALSE parameter means that the negative answer (No) will be the default.
where OptionNumber is the number of the option the user chooses. The first
option in the OptionString is number 1–if the user closes the form with Esc,
STRMENU returns 0 (zero). If it is defined, DefaultNumber is used to select the
default option (if DefaultNumber is not defined, the system will use option
number 1 as the default.)
The statement
Selection := STRMENU('Save,Close,Cancel',3);
Notice that the Cancel option is the default–as the DefaultNumber parameter
was set to 3. It is a good idea to let the default option be a ‘harmless’
action–like Cancel–as this option can be chosen by pressing Enter. If the user
inadvertently presses Enter, no catastrophes will happen, which they might,
if, for example, one of the options was ‘Delete all’.
· Syntax errors
· Run-time errors
· Program logic errors
Traditionally, errors in computer programs are called bugs (as they can be just
as annoying), and the process of finding and correcting errors is, correspond-
ingly, called debugging.
This chapter describes how you can find and eliminate bugs and errors, and it
shows how you use the C/AL debugger to find run-time and program logic
errors.
or this
When you have pressed Enter and acknowledged the error message, the C/AL
editor will appear with the cursor in front of the offending expression. Note
that the error message may not always reflect the nature of the error. Consider
this message:
When you look at the offending code in the editor, if becomes clear that the
error has nothing to do with an unknown variable:
The real error is a misspelling of IF, which has been entered as UF. From the
point of view of the compiler, UF is an unknown identifier, and hence the error
message. When you look at the code, however, it is easy to see what was really
the matter.
The compiler will not compile code that contains any syntax errors, like a
missing THEN in an IF statement, or code that uses undeclared variables.
There is nothing wrong with the syntax–but the statement may cause the
following error:
This error occurs because the Second_number variable has been assigned a
value of 0 (zero), thereby causing a division by zero.
If all three variables are of type integer, the following error could occur:
This error occurs because the result of the division cannot be contained in an
integer. Therefore, the result is converted to decimal–but then the conversion
back to integer (in order to fit the result into the Ratio variable) fails.
The common trait of these errors is that the code can work perfectly in many
situations–and then fail in some. The real danger is that since there is nothing
syntactically wrong with the code, the error could occur when the program is
already in use.
Unless you handle the run-time error in your code, the default messages
shown above will appear. If, as in the example, the division by zero was
attempted using three variables that were assigned values in a simple form,
like this:
in the OnValidate trigger of the Second_number text box, then, after the user
has acknowledged the run-time error by clicking OK, the form will look like
this:
At this point the user cannot move out of the text box where Second_number is
to be entered, or close the form, without changing the value to something
other than 0 (zero).
Run-time errors can roughly be put in two categories: those errors that are
related to the use of data types, and those that occur if a function does not
succeed in doing what it is supposed to do. Division by zero does not fit readily
into either of these categories, but it has been placed in the first one.
The heading of this section is, perhaps, overly optimistic: you can only prevent
some (mainly the data-type-related) errors from occurring at all. Other errors
cannot always be avoided, but you can write code that shields the user from
the error. That is, instead of the default error handling (which amounts to
displaying a message, closing the form that was active when the error occurred
and rolling back any changes to the database), you can write a better error
handler that, for example, gives the user a chance to correct the input that
caused the error, or, at least, displays a message that explains in further detail
why the error occurred.
Data-Type-Related Errors
The most important thing to do to avoid this category of run-time errors is to
use correct data types. Errors like the type conversion error on page 301–and
overflow errors–can be avoided by using the correct data types. In the context
of the example, integer was obviously not a good choice for the Ratio variable.
See Introducing the C/AL Data Types on page 252 for a description of the data
types, and appendix B for a description of how type conversion takes place in
C/SIDE.
The division by zero error on page 301 could have been avoided in several
ways, depending upon the context where the code fragment is used. If the user
enters the denominator (the Second_number variable) in a text box
immediately before the evaluation of the statement, you could test the value of
Second_number before performing the division, and reject a value of 0 (zero):
The return value of the function is Ok–a boolean. If a record is found, Ok will be
TRUE, otherwise it will be FALSE. This return value can be ignored, as indicated
by the square parentheses. If it is ignored, and the requested record cannot be
found, a run-time error will occur and a system-generated error message will
be displayed. If, on the other hand, you test the return value, a run-time error
will not occur, as it is then assumed that you handle the condition yourself.
Example
or like this
you can shield the user from a run-time error. In the first example, if a Customer record with the
given No. cannot be retrieved, an (empty) record is initialized. In the second example, the user is
notified that a record cannot be found and the trigger from where the GET function was called is
exited.
You should only take the examples above as general guidelines–you will have to consider how to
handle situations like these in the context of your own application.
In order to track down a run-time error, you will need an exact description of
the sequence of events that led to the error: that is, what the user was doing at
the time of the error, and what values the user had entered or what record
caused the error.
In can be argued that many, if not most, run-time errors are also program logic
errors. However, the ‘true’ program logic error will not make it itself noticed in
a similarly spectacular way but will quietly generate erroneous data–that may
not always be detected straight away.
When the form is run with sample data, something appears to be wrong. When
compared with the sales order entry forms, the Quantity on the sales statistics
form seems to be incorrectly calculated.
The sum of the quantities for those two sales lines that are visible in the
subform alone is 4 (and there are several lines below those two lines).
The numbers that are shown in the sales statistics form are calculated in the
OnAfterGetRecord trigger of the form, like this:
In this example, the error was easy enough to find, just by looking at the C/AL
code. Using the Debugger to Track Down Errors on page 315 shows how the
debugger can be used to find the error. In a more complex application, this will
be, if not the only way, then the fastest.
If you are trying to track down a run-time error, stepping through the code will
help locate the problematic statement easily, as you can step through the
statements until you arrive at the statement that causes the error.
You can also use the debugger to find a logical error, but finding the error will
not be as easy, and you must have a good understanding of how the code is
supposed to work. If you know what the code should do, then you can use the
possibilities for inspecting the variables at various stages of execution to find
out where the values that are actually used differ from the values you expected
when you designed the application.
The screen resembles the screen in the C/AL editor, but there are some
differences. You can move around with the usual navigation keys and the scroll
bars, and you can put the focus on a statement by clicking on it, but you cannot
edit the code. The column at the far left is used to show the statement that will
be executed next. The indicator is the t. The second column to the left of the
statements contains only bullets in the picture above. It can, however, contain
three different symbols, like this:
Symbol Meaning
There is a disabled breakpoint at this statement.
This is a statement that will be executed.
There is an active breakpoint at this statement.
What Is a Breakpoint?
The basic concept in debugging is the breakpoint. A breakpoint is a mark that
you can set on a statement. When the program flow reaches the statement, the
debugger will intervene and suspend execution until you instruct it to go on.
Without any breakpoints, the code would just run normally when the debugger
is active.
It was mentioned above that you must leave the Breakpoint on Triggers option
on when you activate the debugger. The reason is obvious: if you do not, the
code will be executed normally, since there are no breakpoints. If, however,
the Breakpoint on Triggers option in on, then execution will be suspended
when the first trigger is entered. At this point you can set other breakpoints
and set the Breakpoint on Triggers option to off, if you want.
Setting Breakpoints
Breakpoints are set and cleared by using the Toggle Breakpoint command from
the Run menu (the Run menu is only available while execution is suspended).
Actually, the command is a three-way toggle, corresponding to the three
different breakpoint symbols that can appear in front of a statement (see
above). When Toggle Breakpoint is used, the state will change according to
this sequence:
The state disabled breakpoint means that the breakpoint is still present on the
statement but is momentarily disabled (execution will not stop at this
breakpoint).
All breakpoints can be removed at once by using the Clear All Breakpoints
command from the Run menu.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Breakpoint On Triggers is set independently of other breakpoints–so Clear All
Breakpoints will not affect Breakpoint on Triggers.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Command Meaning
Continue Execution will be resumed and continue till the next breakpoint.
Step Into Statements will be executed one at a time, and you have the opportunity to decide
how to continue after each statement, and the execution will step into any function
that is called. To step into means that the debugger will single-step through the
statements in the function that is called.
Step Over Statements will be executed one at a time, like Step Into, but any called function
will not be stepped into. The functions that are called will be executed completely
before the next statement of the calling function is executed.
Example
The difference between Step Into and Step Over can be seen from an example like. The following is
the C/AL statement about to be executed:
Result := FunctionLib.CalculateResult(a,b,c);
If Step Into is used now, the debugger will open the codeunit (FunctionLib) that contains the
CalculateResult function and the first statement of this function will be the next statement at which
the debugger will stop. If, on the other hand, Step Over is used, FunctionLib.CalculateResult will
simply be executed, and the debugger will next stop at the statement that follows the statement
above.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
if Breakpoint on Triggers is on, execution will be suspended at every trigger
that is entered, even when Step Over is used.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 Choose C/AL Globals from the View menu while execution is suspended to
examine the global variables, or choose C/AL Locals to examine the local
variables of a function.
2 To see all fields of a record, choose Zoom from the Record menu of the
Globals (or Locals) form while the focus is on the record. Click on records to
move the focus.
3 To examine the filters currently in use, choose Table Filter from the Record
menu while the focus is on the record you want to investigate.
4 To see what sorting order is in use, choose Sorting from the Record menu
while the focus is on the record you want to investigate.
when running a form that displays statistics about sales orders–but only sees
it for some orders. The form is only informational, so the user does not enter
any data and cannot point to what exact conditions cause the error. Even if the
user can identify sales orders that will cause the error to occur, it would be
cumbersome to try and find differences between such orders and other orders.
1 Run the form with the same data as the user, until an order that provokes
the error has been identified.
2 The form will close after the error. Open it again, and turn the debugger on
by choosing Active from the Debugger submenu of the Tools menu. Make
sure that Breakpoint on Triggers is on (there will be a check mark next to the
menu item when it is).
3 Step through the C/AL code statement by statement by using Step Into until
the error occurs, thereby identifying the statement that causes the run-time
error.
In the example here, division by zero happens when the statement with the t
mark is executed:
Examining the statement that causes the error makes it clear what the error is.
After testing that AmountLCY is 0 (zero), the variable is used as the
denominator in a division. This ensures that when a customer record is found
where AmountLCY is in fact 0 (zero), the division by zero will occur. The
intention was, however, this:
that is, to perform the division only when AmountLCY is not 0 (zero).
1 Go to the point in the program just before the form with the error is opened,
and turn the debugger on by choosing Active from the Debugger submenu
of the Tools menu. Make sure that Breakpoint on Triggers is on (there will
be a check mark next to the menu item when it is).
2 Open the form where you suspect that there is an error. In the example here,
it is the Sales Statistics form.
As you can see, the debugger has suspended execution on entering the first
trigger of the Statistics form.
3 Open the global variable window by choosing C/AL Globals from the View
menu, and arrange the windows so you can see both the debugger window
and the global variable window.
4 Use Step Into to step through the statements, and observe the variables. In
the example here, the interesting part is when the REPEAT loop is entered
and iterated. After stepping through the loop once, the global variable
window looks like this:
5 Step through the loop once more, and look at the variables:
When you look at the values of the variables, it becomes clear that while the
values of most of the variables have changed, some have not. A number of the
variables have a value of 0 (zero), just as after the first iteration. Inspecting the
code will reveal that this was to be expected in the context. LineQty, the
variable that is the source expression of the Quantity text box, remains at a
value of 2, although every item should have had at least a quantity of 1 (so
LineQty should at least be 3).
The conclusion must be that for some reason, the quantity is not summed as
the loop is repeated. In this limited example, the offending statement is easy to
find, as described in Program Logic Errors on page 307, but you would use a
similar strategy to find logical errors in a larger and more complex application.
324
15.7 Specifications for C/SIDE Application Objects
Maximum number of distinct fields per key 20 for a primary key. The number
of fields in the primary key + the
number of fields in a secondary
key which do not occur in the
primary key must always be less
than or equal to 20.
325
.
326
15.7 Specifications for C/SIDE Application Objects
327
.
328
Appendix B
Type Conversion
The last statement involves one or two type conversions. The right-
hand side of the statement involves the evaluation of the
expression CharVar + integerVar (char + integer). In order to
evaluate this expression, the first operand (CharVar) will have to
be converted from char to integer. The addition operator will then
return an integer result. But if the type of the left-hand side
variable has been declared as, for example, decimal, the result
must be converted from integer to decimal before its value can be
assigned to Sum (this kind of conversion is discussed in
Assignment and Type Conversion on page 262.)
328
15.6 Type Conversion in Expressions
char
char is the least
general numeric
data type
Strings
The text data type is text
more general than code
the code data type
· The most general data types include all possible values from
the less general data types: a decimal is more general than an
integer, which again is more general than a char.
· Type conversion can take place in some cases even though two
operands have the same type.
These rules can be illustrated by some examples.
Example 1
integer + decimal
decimal + decimal
When the left-hand side sub-expression has been converted, the expression
can be evaluated, and the resulting data type will be decimal:
329
.
Example 2
text + code
text + text
When the right-hand side argument has been converted, the expression can
be evaluated, and the resulting data type will be text.
330
15.7 Type Conversion Mechanisms
· Relational operators
· Logical operators
· Arithmetic operators
The following subsections discuss the properties of operators in
C/AL: for each category of operators, there are descriptions of the
valid data types for the arguments and the data types that result
when expressions are evaluated.
Relational Operators
The relational operators are used to compare expressions. The
table below defines the evaluation rules for relational operators.
The rules assume that the data types of the expressions can be
compared–refer to Valid Uses of Relational Operators below for a
complete overview of comparable data types.
331
.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
when using relational operators, upper and lower case letters in
strings are significant. Furthermore, the comparison is based on
the built-in character comparison table of the system, that is, not
by comparing ‘true’ ASCII characters.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The rows in the table show the type of the left argument and the
columns show the type of the right argument. The cells show the
resulting data type.
From the table you can see that a valid use of the relational
operators is, for example, text compared with text or code, while
boolean cannot be compared with anything else than boolean, and
so forth.
Relation
al
boolean
decimal
integer
option
Operator
code
date
time
char
text
boolean boolea - - - - - - - -
n
332
15.7 Type Conversion Mechanisms
Relation
al
boolean
decimal
integer
option
Operator
code
date
time
char
text
s
date - - - - - boolea - - -
n
time - - - - - - boolea - -
n
333
.
binary infix operators; that is, they take two arguments and are
placed between the corresponding arguments.
Arithmetic Operators
Here are some examples of how to use the type conversion rules
for arithmetic operators. The examples illustrate how the
operators are supposed to be used and the effect of the type
conversion made automatically by the C/AL compiler. The
examples have been divided into groups corresponding to the data
types in C/AL.
Example
Note that the same rules apply to option operator expressions as well.
334
15.7 Type Conversion Mechanisms
Example
Example
Example
335
.
The time unit is milliseconds. If time is undefined (0T), a run-time error will
occur.
Example
This table illustrates type conversion in text and code (String) operator
expressions:
This table shows the data types for which the unary operators in
C/AL are defined, and the resulting data types.
336
15.7 Type Conversion Mechanisms
Operator
+ j c c c c c c c c
- j c c c c c c j j
* j c c c c j j j j
/ j c c c c j j j j
DIV j c c c c j j j j
MOD j c c c c j j j j
c Yes, the operator can take at least one operand (left, right or
both) of the given type.
j No, the operator cannot be used with the given type.
The following tables define the valid uses of the binary arithmetic
operators, and the resulting data types.
decimal
The ‘+’
integer
option
code
date
time
char
operator
text
boolean - - - - - - - - -
337
.
boolean
decimal
The ‘+’
integer
option
code
date
time
char
operator
text
integer - integer (C) integer (C) integer decimal (C) - - - -
(C)
date - date(A) (C) date (A) (C) date(A) (C) date(A) (C) - - - -
(D)
decimal
The ‘-’
integer
option
code
date
time
char
operator
text
boolean - - - - - - - - -
338
15.7 Type Conversion Mechanisms
boolean
decimal
The ‘-’
integer
option
code
date
time
char
operator
text
text - - - - - - - - -
code - - - - - - - - -
Note that overflow may occur in all cases in the above table.
A run-time error will occur if the right operand is equal to zero (0).
339
.
A run-time error will occur if the right operand is equal to zero (0).
340
Appendix C
SumIndexFields
· What Is a SumIndexField?
.
340
15.6 What Is a SumIndexField?
A FlowFilter is
used in the =50020
calculation of the
FlowField
300
The same table
50020 01-04-96 300 600
when the FlowFilter
is applied 50020 01-25-96 400 1000
Sum of Amount
column when the 1000 - 300 = 700
filter is applied
341
.
FlowField to sum the Amount field, and the DBMS only needs
retrieve the value from the SumIndexField.
The value 300 is subtracted from the value 1000 to produce the
correct sum (700). No matter how many records there are in the
selected range, the system will always need to perform only two
accesses in order to compute the sum.
342
Appendix D
C/SIDE in Multiuser Environments
Interface
Logical Database
The DBMS controls the interaction of the user with the database to
ensure that a number of data integrity constraints are observed.
By observing these constraints, the DBMS protects your data from
damage or corruption. The DBMS is a very complex unit in your
database system, but the means to obtain data integrity are
centered around a few basic concepts:
344
15.6 Ensuring Data Integrity in a Multiuser Environment
345
15.6 Ensuring Data Integrity in a Multiuser Environment
both of the operations are executed and the data will always be
consistent.
The C/SIDE DBMS does not need to use a log file because the
C/SIDE database is data-version oriented. This means that each
time a transaction is committed, a new version of the database is
created. While you enter new data in the database your changes
are private; not until you commit the changes, does the new data
become public and establish the newest version. The DBMS allows
different applications to access and modify the database
concurrently by letting them work on individual versions that are
snapshots of the database taken at the point in time where the
applications start to access the database. The advantages of the
data version approach will become clear as you read through the
following sections.
346
15.6 Ensuring Data Integrity in a Multiuser Environment
Version A B C D
Time
(A)
Three applications Report
accessing different
versions of the
database
Entry
(B)
Backup
347
15.6 Ensuring Data Integrity in a Multiuser Environment
the database. In this way the DBMS allows for concurrency while
still maintaining read consistency. If the accesses involve only data
retrieval and no changes, then the newest version will persist for
all applications. There will be no new version until a write
transaction is performed.
Example
Imagine that the tree structure in your database contains a branch with
customers A, B and C. Furthermore there are two free database blocks
available.
Assume that you need to modify customer A and C. When you update the
records, the DBMS makes a copy of the original. As illustrated in the figure
below, the copies will use two free database blocks. You will then modify the
copies, and the system will create a new internal node.
348
15.6 Ensuring Data Integrity in a Multiuser Environment
Database
Version 1
A B C Free
Free
Database
Version 2
A B C C1
A1
If an error occurs during the transaction, or the user decides to abort the
changes, the database blocks occupied by the copied branch will be freed
and be available for new database updates.
If the transaction is committed, this new internal node will replace the old
node, and the database blocks used by the old versions of customer A and B
will now be available as free database blocks that can be used by database
updates.
349
15.6 Ensuring Data Integrity in a Multiuser Environment
350
15.6 Ensuring Data Integrity in a Multiuser Environment
.
.
BeginWriteTransaction;
LockTable(TableA) (1)
FindRec(TableA, ...); (2)
The scope of .
write locks .
InsertRec(TableA, ...) (3)
Table A locked
.
InsertRec(TableB, ...); (4)
.
Table B locked
.
.
.
.
EndWriteTransaction (5)
.
.
This illustrates both an explicit lock and an automatic lock. Line (1)
in the write transaction explicitly locks table A. If this explicit lock
were not set on table A, the DBMS would automatically lock this
table when a record is inserted (3). Table B is not locked explicitly,
but is locked automatically by the DBMS when a record is inserted
(4). Both locks are active until the EndWriteTransaction command
is executed in line (5).
351
15.6 Ensuring Data Integrity in a Multiuser Environment
Application 1 Application 2
AA AA
AA AA
AA
AA AA
Time
AA AA
AA
AA AA
AA AA
AA
AA
AA AA
AA
AA
AA
LockTable(A) AA
AA
AA
AA
AA
AA
AA
AA
AA
Table A now Locked
AA
AA
AA
AA
AA
LockTable(B)
AA
AA
AA
AA
AA
AA
Table BAAnow locked
AA
AA
LockTable(B) AA
AA
AA
AA
AA
AA
AA
AA
AA
Wait for table B to AA
be unlocked LockTable(A)
AA
AA
AA
AA
AA
AA
AA
AA
Wait for table A to
AA
AA
AA
AA
AA
AA
AA
be unlocked
AA
AA AA
AA
AA AA
AA AA
AA
AA AA
AA
AA AA
AA
AA
AA AA
AA AA
AA
AA AA
AA
AA AA
AA
AA
AA
AA
AA
AA
AA
Application 2 ejected
AA
AA
AA
AA
AA
and transaction aborted.
AA
AA
AA
AA
AA
Lock attempt on Table A
AA
AA
AA
AA cancelled and Table B
AA
AA
AA
AA
AA
AA
AA
automatically unlocked
AA
AA
AA
AA
Table B locked
AA
AA
AA
AA
AA
AA
AA
AA
AA
AA
AA
AA
AA
The DBMS will always eject the application which causes the
deadlock to occur–as in the example above. This rule applies for
any number of applications involved in a deadlock.
352
15.6 Ensuring Data Integrity in a Multiuser Environment
353
15.6 Ensuring Data Integrity in a Multiuser Environment
Differences in
committing updates
in C/AL and C code
C/AL code C code
BeginWriteTransaction
DBL_BWT();
C/AL Module
Commit(...)
C/AL Statements
} 2. Trans.
DBL_BWT();
C code
} 2. Trans.
EndWriteTransaction DBL_EWT();
354
Appendix E
Performance
356
15.6 The DBMS Cache
Network
DBMS
DB Request Handler
Database
: Data flow
The size of the cache has great impact on the performance. Two
simple rules apply when setting the size of the cache:
· The more memory you assign to the cache, the more efficient it
will be. (Of course there is no reason to assign more memory to
the cache than the total size of your database.)
· The size of the cache should not exceed the amount of physical
memory available on your system, as this may cause the
357
.
358
15.7 The Commit Cache
However, when a power failure occurs, you will lose all committed
transactions currently held in the commit cache.
359
.
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
you should not use advanced disk caches with delayed write back
(sometimes called lazy write). Using such cache systems may
cause your database file to be corrupted.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The figure below illustrates a database stored on three physical
disks. Each disk is controlled by its own commit cache process. All
processes are connected to enable parallel reading and writing.
DBMS
c: d: f:
360
15.8 The Command Buffer
Notice that...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
the contents of the command buffer are sent to the DBMS when
the buffer is full, or when a command requires an immediate
response from the DBMS.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The advantage of assembling DBMS commands into packages is
that the number of network transfers will be reduced (that is, the
load on the LAN will be reduced) as the time required to send one
DBMS request is comparable to the time used to send an entire
package.
WHILE Record.FIND('-') DO
Record.DELETE();
Two commands are executed for each record in the table, but each
record will only cause one request to be sent to the DBMS, as the
DELETE command will be stored in the command buffer until the
FIND command is executed.
361
.
Debugging The system automatically turns off the command buffer when you
activate the C/AL debugger. This can lead to some confusion if you
are not aware of this fact.
Customer."No." := '12';
Customer.DELETE();
First := 7;
Second := 0;
Ratio := First / Second;
362
Appendix F
Report Flow Charts
Legend
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Entry point
Exit
Process
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
364
15.7 Report.Run
15.7 Report.Run
365
.
Report.Run
Call Init
Trigger
Set DataItem
TableViews
ReqForm.Run
Cancel
OK / Print / Preview
BWT
Set DataItem
ReqFilters
Call PreReport
Trigger
Clear DataItem
Filters
PageNo=1
DataItem.Run
No more
Call PostReport
EWT
Trigger
366
15.8 DataItem.Run
15.8 DataItem.Run
DataItem.Run
Clear DataItem
Property:
True Hold
PrintOnlyIfDetail
Set DataItem
TableView False
Body.Run
Set DataItem
ReqFilter
Transfer DataItem
Link DataItem.Run
Header.Run
No
Get next lower
OK
DataItem
TotalArray:
Append Group/ (OnlyFirst)
Grand totals
No more
TotalArray:
Update Group/
Call PreDataItem
Grand totals
Trigger OK GroupHeader.Run
NewPage
Property:
True
TotalArray: NewPagePerRecord
Clear Totals
False
GetRecord
Property:
Skip / Break True RollBack
PrintOnlyIfDetail
Skip
Break False
NextRec =
No more
NextRec = NULL GetNextRec(Curr
Rec)
CurrRec = OldRec
GroupFooter.Run
TotalArray:
SwapTotal
(GrandTotal)
Call PostDataItem
Trigger
Footer.Run
(OnlyLast)
367
.
15.9 Section.Run
Section.Run (Transport)
OK
Call PreSection
Trigger
NewPage
Calculate Space
Not Enough
Need (Param:
Transport)
Enough
Call PostSection
Trigger
Print Section
368
15.10 Header.Run
15.10 Header.Run
Header.Run (IncludeBack,UsePrintOnEveryPage,OnlyFirst)
Param: Not
True OldRec
OnlyFirst NULL
NULL
False
Hold
Back.Header.Run
Param:
True
IncludeBack
(IncludeBack)
False
Param:
Property:
UsePrintOnEvery True False
PrintOnEveryPage
Page
False
True
Section.Run
369
.
15.11 Footer.Run
Footer.Run (IncludeBack,UsePrintOnEveryPage,OnlyLast)
Param: Not
True NextRec
OnlyLast NULL
False
NULL
Param:
Property:
UsePrintOnEvery True
PrintPnEveryPage
Page
False
True
Property:
True Move to Bottom
PlaceInBottom
False
TotalArray:
SwapTotal
(GrandTotal)
False
Section.Run
TotalArray:
SwapTotal
(GrandTotal)
Param:
IncludeBack
True
False
Back.Footer.Run
(IncludeBack)
Param: Not
True NextRec
OnlyLast NULL
NULL
False
RollBack
370
15.12 TransHeader.Run
15.12 TransHeader.Run
371
.
TransHeader.Run (IncludeBack)
Back.Trans
Header.Run
Param:
True
IncludeBack
(IncludeBack)
False
OldRec NULL
Not NULL
TotalArray:
SwapTotal
(LastGroup)
Section.Run
TotalArray:
SwapTotal
(LastGroup)
372
15.13 TransFooter.Run
15.13 TransFooter.Run
TransFooter.Run (IncludeBack)
OldRec
Not NULL
TotalArray:
SwapTotal
(LastGroup)
Section.Run
NULL
TotalArray:
SwapTotal
(LastGroup)
Param:
False
IncludeBack
True
Back.Trans
Footer.Run
(IncludeBack)
373
.
15.14 GroupHeader.Run
374
15.14 GroupHeader.Run
GroupHeader.Run
Get next
GroupTotalField No more
(forward in key)
OK
Check
Not
GroupTotalField
Changed
(OldRec-CurrRec)
Changed
Hold
NewPage
Property:
Not
OldRec NewPagePer True
NULL
Group
NULL False
TotalArray:
SwapTotal
(LastGroup)
Section.Run
TotalArray:
SwapTotal
(LastGroup)
375
.
15.15 GroupFooter.Run
GroupFooter.Run
Get next
No
GroupTotalField
more
(backward in key)
OK
Check
GroupTotalField Changed
(CurrRec-NextRec)
TotalArray:
Not
SwapTotal
Changed
(GroupTotal)
Section.Run
(Transport)
TotalArray:
SwapTotal
(GroupTotal)
TotalArray:
GroupChange
RollBack
376
15.16 Body.Run
15.16 Body.Run
Body.Run
Section.Run
Property:
True
PrintOnlyIfDetail
(Transport)
False
Commit
377
.
15.17 NewPage
NewPage (Transport)
TransFooter.Run
Param:
True
Transport
(IncludeBack)
False
Footer.Run
(IncludeBack)
(UsePrintOnEvery
Page)
PageBreak
Increment PageNo
Header.Run
(IncludeBack)
(UsePrintPn
EveryPage)
TransHeader.Run
Param:
True
Transport
(IncludeBack)
False
378
15.18 GetRecord
15.18 GetRecord
GetRecord
Yes
TotalArray:
ClearTotalVars
CurrRec =
No
GetNextRec
more
(CurrRec)
No
OK more
Auto Calculate
FlowFields
Skip = No
Break = No
Call
AfterGetRecord
Trigger
OK
379
.
380
Index
A statements 251
application Symbol Menu 243
application object 8 syntax errors 299
design 14 where to place 278
design (reference to books) 20 C/AL debugger 310
object 5 C/AL functions
the term 8 CALCFIELDS 288
array 257 CALCSUMS 289
ascending order 38 CONFIRM 295
DELETE 287
B DELETEALL 288
ERROR 295
binary (field data type) 28
FIELDERROR 289
BLOB 29
FIELDNAME 290
C/AL data type 256
FIND 283
field data type 29
GET 282
boolean
GETRANGEMAX 285
C/AL data type 253
GETRANGEMIN 285
displaying 130
INIT 291
field data type 28
INSERT 286
bound control 91, 122
LOCKTABLE 288
bound form 91
MESSAGE 294
breakpoint 311
MODIFY 286
bugs 298
MODIFYALL 287
NEXT 283
C
OPEN 294
C/AL
overview 282
bugs 298
SETCURRENTKEY 284
comments 275
SETFILTER 285
constants 258
SETRANGE 284
control language 268
STRMENU 295
data types 253
TESTFIELD 291
debugger 310
UPDATE 294
debugging 298
VALIDATE 291
defined 9, 250
cache 356
dialogs 292
commit cache 358
editor 7, 236
DBMS cache 356
essential functions 282
CalcFormula (property) 45
expressions 251
calculation formula 45
function calls 266
card form 93
Globals 241
creating 94
Locals 242
CASE 270
operator hierarchy 266
char (C/AL data type) 254
operators 264
check box to display booleans 130
program logic errors 307
code
repetitive statements 271
C/AL data type 255
reusing code 279
field data type 28
run-time errors 301
377
Index
378
Index
379
Index
380
Index
381
Index
382
Index
xRec 280
virtual table 76
advanced 77
Database File 83
Date 77
Drive 79
File 78
Integer 78
Monitor 79
Session 81
simple 77
Table Information 84
virtual tables 214
W
WHILE DO 272
WITH 274
write locks 350
write transaction 344
383
Index
384