The form is based on the FormRun class. We can see this by opening (double-click) classDeclaration for the CustGroup form:
public class ConWHSVehicleParameters extends FormRun
This is actually a definition file that FormRun instantiates with in order to build and execute the form.
Once the system has built the form design using SysSetupFormRun, it will perform the initialization tasks and run the form. The key methods in this are Init and Run. These can be overridden on the form in order to perform additional initialization tasks.
One of FormRunInit method's key tasks is to construct the form's data sources; these aren't table references but FormDataSource objects constructed from the tables listed under the Data Sources node.
In the case of the ConWHSVehicleParameters form, the system creates the following objects from the ConWHSVehicleParameters data source for us:
- ConWHSVehicleParameters as type ConWHSVehicleParameters(table)
- ConWHSVehicleParameters_DS as type FormDataSource
- ConWHSVehicleParameters_Q as type Query
- ConWHSVehicleParameters_QR as type QueryRun
We aren't normally concerned with the Query and QueryRun objects as we can access them through the FormDataSource object anyway.
The data sources are declared as global variables to the form and provide a layer of functionality between the form and the table. This allows us to control the interaction between the bound form controls and the table.
Let's override the init method on the data source, and then override the modifiedField event on a field; the code editor will present the change as follows:
[Form]
public class ConWHSVehicleParameters extends FormRun
{
public void init()
{
ConWHSVehicleParameters::Find();
super();
}
[DataSource]
class ConWHSVehicleParameters
{
public void init()
{
super();
}
[DataField]
class DefaultVehicleGroupId
{
public void modified()
{
super();
}
}
}
}
It adds the data source as a class within the form's class declaration, and then adds the field as a class within the data source class. This is the only place in Operations where this occurs. If the data source class were a class, it would have to extend FormDataSource. The Form, DataSource, and DataField attributes are a clue as to what's going on here. As all executable code compiles to CLR types, the compiler uses these attributes in order to create the actual types. The structure is written as such for our convenience as a presentation of code.
Let's take the modifiedField method. This is an event that occurs after the validateField event returns true. The call to super() calls the table's modifiedField method. We may wonder why the call to super() has no parameter. This happens behind the scenes, and it is useful that this is handled for us.
This pattern is followed for the following methods:
DataSource method |
Calls table method |
validateWrite |
validateWrite |
write |
write (in turn, insert or update) |
initValue |
initValue |
validateDelete |
validateDelete |
delete |
delete |
DataField.validateField |
validateField(FieldId) |
DataField.modifiedField |
modifedField(FieldId) |
The table's initValue, validateField, modifiedField, validateWrite, and validateDelete methods are only called from form events; the write method does not call validateWrite.
From this, we have a choice as to where we place our code, and this decision is very important. The rule to follow here is to make changes as high as possible: table, data source, and form control.
We can go further with this and write form interaction classes that allow user interface control logic to be shared across forms; for instance, controlling which buttons are available to a list page and the associated detail form.