You are on page 1of 50

Chapter 3: Classes

CHAPTER 3: CLASSES
Objectives
The objectives are:

• Use collection classes to store data in X++.


• List which application objects control different Graphical User
Interface (GUI) components.
• Modify and use the Application Substituted Kernel Classes.
• Extend the RunBase framework to create new batch processes.
• Transfer information using the Args object.

Introduction
Microsoft Dynamics® AX provides a large range of standard system classes that
you can use while developing X++ code. This lesson introduces some of the most
commonly used system classes, and demonstrates ways they can be used to
support modifications.

3-1
Development III in Microsoft Dynamics® AX 2012

Collection Classes
X++ contains two compound data types: arrays and containers. They are useful
for aggregating values of simpler data types. However, you cannot store objects
in arrays or containers. The Microsoft Dynamics AX collection classes have been
designed for storing objects.

The following collection classes are available:

• List
• Map
• Set
• Array
• Struct

The following classes also aide in aggregating data:

• RecordSortedList
• RecordInsertList

List
A List object contains members that are accessed sequentially. Lists are
structures that can contain members of any X++ type. All the members in the
same list must be of the same type.

The following methods are commonly used on List objects:

• addEnd(anytype) - adds a member to the end of the list.


• addStart(anytype) - adds a member to the start of the list.
• elements() - returns the number of members contained in the list.
• getEnumerator() - returns a ListEnumerator object for this List
object.
• typeId() - returns the Type that the List contains.

The ListEnumerator class allows you to traverse through the elements within a
list. The following methods are commonly used on ListEnumerator objects:

• current() - retrieves the value of the item currently pointed to in the


list.
• moveNext() - moves the enumerator to the next item in the list. List
enumerators start before the first element in the list, so moveNext()
must be called to make it point to the first element in the list.
• reset() - moves the enumerator to the start of the list.

3-2
Chapter 3: Classes

The following example demonstrates how to create a list, then move through it
and extract values from it.

1. Create a new List object, specifying the data type it will contain.
2. Add members to the List object, using the List.addEnd() and
List.addStart() methods.
3. Set the ListEnumerator object using the List.getEnumerator()
method.
4. Go to the start of the list, using ListEnumerator.reset().
5. Move through the members of the list, using
ListEnumerator.moveNext().
6. Pull the value of the current member in the list, using
ListEnumerator.current().

List integerList = new List(Types::Integer);


ListEnumerator enumerator;

// Add some elements to the list


integerList.addEnd(1);
integerList.addEnd(2);
integerList.addStart(3);

// Set the enumerator


enumerator = integerList.getEnumerator();

// Go to beginning of enumerator
enumerator.reset();

//Go to the first element in the List


enumerator.moveNext();

// Print contents of first and second elements


info(strfmt("First element is %1", enumerator.current()));
enumerator.moveNext();
info(strfmt("Second element is %1", enumerator.current()));

When the code is executed, the result in the infolog should be as follows:

• First element is 3
• Second element is 1

Map
A Map object associates one value (the key) with another value. Both the key
and value can be of any valid X++ type, including objects. The types of the key
and value are specified in the declaration of the map. The way in which maps are
implemented means that access to the values is very fast.

3-3
Development III in Microsoft Dynamics® AX 2012

Multiple keys can map to the same value, but one key can map to only one value
at a time. If a [key, value] pair is added where the key already exists, it will
replace the existing pair with that key value.

The following methods are commonly used on Map objects:

• insert(_key, _value) - inserts a member (pair) into the map, where


_key and _value can be anytype.
• remove(_key) - removes a [key, value] pair from a map.
• exists(_key) - determines whether a particular value exists as a key
in the map.
• lookup(_key) - returns the value mapped to by a particular key
value.
• elements() - returns the number of members contained in the map.
• getEnumerator() - returns a MapEnumerator object for this Map
object.

The MapEnumerator class allows you to traverse through the members of a


map. The following methods are commonly used on MapEnumerator objects:

• currentKey() - retrieves the key of the pair currently pointed to in


the map.
• currentValue() - retrieves the value of the pair currently pointed to
in the map.
• moveNext() - moves the enumerator to the next pair in the map. Map
enumerators start before the first pair in the map, so moveNext()
must be called to make it point to the first pair in the map.
• reset() - moves the enumerator to the start of the map.

The following example demonstrates how to create a map of Customers per


State, then move through it and extract values from it.

1. Create a new Map object, specifying the data type it will contain.
2. Loop through the records in the CustTable. For each, use
Map.exists() to check if CustTable.stateName() already exists as a
key in the map.
3. If it does not already exist, use Map.insert() to insert a pair of
(CustTable.stateName(), 1).
4. If it does already exist, use Map.insert() to re-insert the pair with the
same key (which will overwrite the existing pair), but the new value
equals the existing pair value plus 1.
5. Set the MapEnumerator object using the Map.getEnumerator()
method.
6. Go to the start of the map, using MapEnumerator.reset().

3-4
Chapter 3: Classes

7. Move through the members of the map, using


MapEnumerator.moveNext().
8. Pull the value of the current member in the map, using
MapEnumerator.currentKey() and
MapEnumerator.currentValue().

Map mapStateNumbers;
MapEnumerator enumerator;
CustTable custTable;

mapStateNumbers = new Map(Types::String, Types::Integer);

while select custTable


{
if(mapStateNumbers.exists(custTable.stateName()))
{
mapStateNumbers.insert(custTable.stateName(),
mapStateNumbers.lookup(custTable.stateName())+1);
}
else
{
mapStateNumbers.insert(custTable.StateName(), 1);
}
}

enumerator = mapStateNumbers.getEnumerator();

while (enumerator.moveNext())
{
info(strfmt("%1 customers are located in %2.",
enumerator.currentValue(), enumerator.currentKey());
}

Set
A Set is used for the storage and retrieval of data from a collection in which the
members are unique. The values of the members serve as the key according to
which the data is automatically ordered. Thus, it differs from a List collection
class where the members are placed into a specific position, and not ordered
automatically by their value.

Set members may be of any X++ type. All members in the set must have the
same type.

When a value that is already stored in the set is added again, it is ignored and
does not increase the number of members in the set.

The following methods are commonly used on Set objects:

• add(anytype) - inserts a value into the set.


• remove(anytype) - removes a value from the set.

3-5
Development III in Microsoft Dynamics® AX 2012

• in(anytype) - determines whether a particular value is a member of


the set.
• elements() - returns the number of members contained in the set.
• getEnumerator() - returns a SetEnumerator object for this Set
object.

The SetEnumerator class allows you to traverse through the members within a
set. The following methods are commonly used on SetEnumerator objects:

• current() - retrieves the value currently pointed to in the set.


• moveNext() - moves the enumerator to the next value in the set. Set
enumerators start before the first value in the set, so moveNext()
must be called to make it point to the first value in the set.
• reset() - moves the enumerator to the start of the set.

The following example demonstrates how to create two sets, and then compares
each set to detect any shared members and remove them.

1. Create two new Set objects, specifying the data type they will
contain.
2. Add members to the Set objects, using the Set.insert() method.
3. Set a SetEnumerator object for one of the Sets, using the
Set.getEnumerator() method.
4. Go to the start of the set, using SetEnumerator.reset().
5. Move through the members in the set, using
SetEnumerator.moveNext().
6. Pull the value of the current member in the set, using
SetEnumerator.current().
7. Search for the value in the second set, using Set.in(). If found,
remove it using Set.remove().

Set setOne;
Set setTwo;
SetEnumerator enumerator;
Int value;

setOne = new Set(types::Integer);


setOne.add(1);
setOne.add(2);
setOne.add(3);

setTwo = new Set(Types::Integer);


setTwo.add(3);
setTwo.add(4);
setTwo.add(5);

enumerator = setOne.getEnumerator();

3-6
Chapter 3: Classes

while (enumerator.moveNext())
{
value = enumerator.current();

if (setTwo.in(value))
{
setTwo.remove(value);
}
}

Array
Array objects may hold values of any single type, including objects and records.
Objects of this type may be transferred to functions and methods. The values are
stored sequentially.

The following methods are commonly used on Array objects:

• exists(int) - checks if a value exists in a specific position in the


array.
• lastIndex() - retrieves the highest index that is used to store a value
in the array.
• value(int _index, [anytype _value]) - gets or sets the value of the
array member that is stored at the specified index.
• typeId() - returns the data type that the Array contains.

The following is an example using an Array object that holds three different
Query objects:

Array array = new Array (Types::Class);

array.value(1, new Query());


array.value(2, new Query());
array.value(3, new Query());

Struct
Struct objects (short for structures) are entities that can hold a number of values
of any X++ type. Structs are used to group information about a specific entity.
For example, there may be a need to store information about a customer's name,
group and address and then treat this compound information as one item.

The following methods are commonly used on struct objects:

• add(str _fieldName, anytype _value) - adds a new field to the


struct and assigns the specified value to it.
• exists(str _fieldName) - determines whether a particular field exists
in a struct.
• fieldName(int _index) - returns the name of the field in the struct at
the position specified.

3-7
Development III in Microsoft Dynamics® AX 2012

• fields() - returns the number of fields in the struct.


• index(str _fieldName) - calculates the position of a field in the
struct based on its name.
• remove(str _fieldName) - removes a field from a struct.
• value(str _fieldName, anytype _value) - gets or sets the value for a
specific field.
• valueIndex(int _index, anytype _value) - gets or sets the value of
the field at a specific index position in a struct.

The fields in a struct are specified by a data type and a name. Fields can be
added when the struct is created by using the new() method, or they can be
created after the struct has been created using the add() method. If a field is
added using the add() method, the value is specified for the field at the same
time, and the data type is inferred from this value. If a field is added during the
instantiation of the struct, the value() or the valueIndex() method needs to be
used to assign a value to it.

The fields in a struct are arranged in alphabetical order of the field names.

The following is an example using the Struct object.

Struct struct = new Struct();


int i;

// Add new fields and values


struct.add("Group", "DOM");
struct.add("Name", "Jane Doe");
struct.add("Shoesize", 45);

// Prints the type and name of all items in the struct


for (i = 1 ; i <= struct.fields() ; i++)
{
info(strfmt("%1 : %2", struct.fieldType(i),
struct.fieldName(i)));
}

The result in the infolog should be:

• String : Group
• String : Name
• Integer : Shoesize

3-8
Chapter 3: Classes

RecordSortedList
The RecordSortedList class inserts multiple records in a single database trip,
and can hold a subset of data from a table in a particular sort order that does not
exist as an index.

A RecordSortedList object holds records from a single table. The list has a
unique key that is defined by the fields listed by using the sortOrder method.
Records are automatically sorted as they are inserted, they do not have to be
inserted in sort sequence.

RecordSortedList objects are particularly useful for passing a result-set as a


parameter.

There is no limit to the size of a RecordSortedList object, but they are


completely memory-based, so there are potential memory consumption problems.

Use a RecordSortedList in preference to a temporary table when:

• Only one sort order is needed.


• The number of records is not too high (to avoid memory problems).

Compared to temporary tables, RecordSortedList objects:

• Are faster
• Are not disk-based
• Only have one index
• Cannot be used in forms
• Require a call between the client and server per (grouped) read

The following methods are commonly used on RecordSortedList objects:

• del(common) - removes a record that has a key that matches the key
fields in the recordBuffer from the RecordSortedList.
• find(common) - sets the recordBuffer to the contents of the record
that has a key that matches the key fields in the recordBuffer, and
positions the list to the record returned.
• first() - positions the list to the first record in the list, and copies its
contents to the recordBuffer.
• ins(common) - inserts a new record in a RecordSortedList, unless it
is a duplicate.
• insertDatabase() - inserts multiple records on a single trip to the
database.
• len() - returns the current number of records in a RecordSortedList
object.
• next() - sets the record buffer to the contents of the next record in the
RecordSortedList and positions the list to the record returned.

3-9
Development III in Microsoft Dynamics® AX 2012

• sortOrder(fieldId, ...) - defines the fields on which the records are


sorted.
• sortOrderFromContainer(container) - defines the field(s) on
which the records are sorted.

The following code sample demonstrates how to insert multiple CustTable


records in the same database trip, using RecordSortedList.

RecordSortedList recordSortedList;
CustTable custTable;

recordSortedList = new
RecordSortedList(tablenum(CustTable));

custTable.clear();
custTable.AccountNum = "123";
custTable.CustGroup = "DOM";
custTable.Currency = "USD";
recordSortedList.ins(custTable);

custTable.clear();
custTable.AccountNum = "456";
custTable.CustGroup = "DOM";
custTable.Currency = "USD";
recordSortedList.ins(custTable);

custTable.clear();
custTable.AccountNum ="789";
custTable.CustGroup ="INT";
custTable.Currency ="USD";
recordSortedList.ins(custTable);

recordSortedList.insertDatabase();

RecordInsertList
The RecordInsertList class provides array insert capabilities in the kernel. This
allows you to insert more than one record into the database at a time, which
reduces communication between the application and the database.

Records are inserted only when the kernel finds the time appropriate, but they are
inserted no later than the call to the add() and insertDatabase() methods. These
same methods also return the accumulated number of records that are currently
inserted, which allows you to keep track of when the records are actually
inserted.

The array insert operation automatically falls back to classic record-by-record


inserts when non-SQL based tables are used (for example, temporary tables), or
when the insert method on the table has been overridden.

3-10
Chapter 3: Classes

RecordInsertList is similar to RecordSortedList, but it has built-in client/server


support (it automatically packs data from one tier to another when needed), and it
lacks the sort order features available in RecordSortedList.

The following methods are commonly used on RecordInsertList objects:

• add(common) - inserts a new record in a RecordInsertList.


• insertDatabase() - inserts multiple records on a single trip to the
database.

The following is an example using RecordInsertList to copy a bill of materials


(BOM) from one BOM to another.

void copyBOM(BOMId _fromBomId, BOMId _toBomId)


{
RecordInsertList bomList;
BOM bom, newBom;

bomList = new RecordInsertList(tableNum(BOM));

while select bom


where bom.BOMId == _fromBomId
{
newBom.data(bom);
newBom.BOMId = _toBomId;
bomList.add(newBOM);
}

bomList.insertDatabase();
}

3-11
Development III in Microsoft Dynamics® AX 2012

Lab 3.1 - Create a Map


Scenario

Isaac, the Systems Developer, has been asked to create a function that will
quickly inform a user how many customers and vendors are located in each
country. This is a key business indicator for the company.

Challenge Yourself!
Make a map that holds a Country Name and a count of all Customers that have a
postal address in that Country. Enumerate through the map and print the result.

Need a Little Help?


Create a class that has a method that will search through all customers and find
the country in the postal address. The custTable table has a method called
postalAddress(), that will return a record with the address. For each country,
lookup in the map to see if it has already been counted. If it has, then add one to
the total for that country. If it has not, add a new key\value pair in to the map.

Create another method that will enumerate through the key\value pairs and
output both values to the infolog.

Create a method that will call both methods and a main method so that the class
can be called from a menu.

Step by Step

1. In the AOT create a new class called CreateCountryCountMap.


2. Add the following code to the classDeclaration and to four new
methods called searchCustomers, loopCountries, run and main.
3. Save your changes to the class.
4. Right-click on the class and select Open.
5. Verify the results are what you would expect.

public class CreateCountryCountMap


{
Map countryMap;
}

private void searchCustomers()


{
CustTable custTable;
Counter cnt;
LogisticsAddressCountryRegionId countryId;
LogisticsAddressCountryRegionName countryName;

countryMap = new Map(Types::String, Types::Integer);

3-12
Chapter 3: Classes

while select custTable


{
countryId =
custTable.postalAddress().CountryRegionId;
countryName =
LogisticsAddressCountryRegion::find(countryId).displayName(
);

cnt = 0;

if (countryMap.exists(countryName))
{
cnt = countryMap.lookup(countryName);
countryMap.remove(countryId);
}

cnt++;
countryMap.insert(countryName, cnt);
}
}

private void loopCountries()


{
MapEnumerator mapEnumerator = new
MapEnumerator(countryMap);

while (mapEnumerator.moveNext())
{
info(strFmt("There are %1 customers in %2",
mapEnumerator.currentValue(), mapEnumerator.currentKey()));
}
}

private void run()


{
this.searchCustomers();
this.loopCountries();
}

static void main(Args _args)


{
CreateCountryCountMap countryCount = new
CreateCountryCountMap();

countryCount.run();
}

3-13
Development III in Microsoft Dynamics® AX 2012

Application Object Classes


These classes hold functions that are activated whenever you use the AOT to
create your application. For example, the system uses the FormDesign class
when you define the layout of your form in the Designs node in the AOT. When
you click New CheckBox in the AOT, the system activates the controlName
method with a CheckBox type control as parameter.

These classes also enable you to create and modify application objects.

Form Classes
The Form classes enable you to manipulate, create, modify, or run forms by
using X++ code. You can also modify forms during run time so, for example, one
or more controls are hidden on a form, depending on the user's selections in the
preceding form.

The Form classes are all system classes and are prefixed with Form. For
example, FormRun, FormStringControl.

The most important classes are described in the following table:

System class name Description


Form This class contains property methods for the form
name and other form properties and methods that
enable addition of data sources and controls to the
form.
Use this class to create a form from your X++
code rather than by using the AOT.
FormRun This class contains methods for executing a form.
FormDesign This class contains property methods for the
properties on the Design node for a form and
methods for creating controls.
Used at form runtime.
FormBuildDesign This class contains property methods for the
properties on the Design node for a form and
methods for creating controls.
Use this class to create the graphical layout of a
form by using X++ code.
Used to design a form.
FormDataSource This class contains property methods for the
properties on a form data source and methods for
changing the behavior of a data source (such as
caching, validation, and so on) and notification
methods for events on records in forms.
Used at form run-time.

3-14
Chapter 3: Classes

System class name Description


FormBuildDataSource This class contains property methods for the
properties on a form data source. Use this class to
manipulate the properties on a form data source
by using X++ code.
Used to design a form.
FormControl FormControl is the parent class for all the other
form control classes. Use methods on the class for
the relevant control type rather than this parent
class.
Used at form run-time.
Form<control These classes contain property methods for an
name>Control individual control type and methods to manipulate
(e.g. FormStringControl, that control type.
FormTreeControl, etc) Used at form run-time.
FormBuild<control These classes contain property methods for an
name>Control individual control type.
(e.g. Used to design a form.
FormBuildStringControl
,
FormBuildTreeControl,
and so on.)

The following example demonstrates how to create and then run a form using
X++ code:

1. Create a Form object.


2. Add a datasource to the form using Form.addDataSource().
3. Add a design to the form using Form.addDesign().
4. Add the caption "Customer information" to the design using
FormBuildDesign.caption().
5. Add a Tab to the design using FormBuildDesign.addControl().
6. Add two Tab Pages to the Tab using
FormBuildTabControl.addControl().
7. Add the captions "Overview" and "General" to the Tab Pages using
FormBuildTabPageControl.caption().
8. Add a grid to the first Tab Page using
FormBuildTabPageControl.addControl().
9. Add two strings to the second Tab Page using
FormBuildTabPageControl.addControl().
10. Add data fields (for AccountNum, Phone, Name and Address) to the
grid using FormBuildGridControl.addDataField(), referencing the
FormBuildDataSource object.

3-15
Development III in Microsoft Dynamics® AX 2012

11. Set data sources to the string controls using


FormBuildStringControl.dataSource() and .dataField(),
referencing the FormBuildDataSource object.
12. Create a new Args object.
13. Pass Form object into Args.object().
14. Use ClassFactory.formRunClass(args) to create a FormRun class.
15. Run and detach the FormRun object.
16. The method should look as follows:

static void createForm()


{
Args args;
Form form;
FormRun formRun;
FormBuildDesign fbDesign;
FormBuildDataSource fbDS;
FormBuildGridControl fbGrid;
FormBuildStringControl fbStr1;
FormBuildStringControl fbStr2;
FormBuildTabControl fbTab;
FormBuildTabPageControl fbTabPage1;
FormBuildTabPageControl fbTabPage2;
DictTable dictTable;
int idx, idx2, idx3;

FormControlType fctTabPage = FormControlType::TabPage;


FormControlType fctTab = FormControlType::Tab;
FormControlType fctGrid = FormControlType::Grid;
FormControlType fctString = FormControlType::String;

// Create the form header.


form = new Form();

// Add a data source to the form.


dictTable = new DictTable(tablenum(CustTable));

fbDS = form.addDataSource(dictTable.name());
fbDS.table(dictTable.id());

// Create the form design.


fbDesign = form.addDesign("Design");
fbDesign.caption("Customer information");

// Add tab
fbTab = fbDesign.addControl(fctTab, "Overview");

// Add tab pages


fbTabPage1 = fbTab.addControl(fctTabPage, "Overview");
fbTabPage1.caption("Overview");

3-16
Chapter 3: Classes

fbTabPage2 = fbTab.addControl(fctTabPage,"General");
fbTabPage2.caption("General");

// Add grid onto tab 1 and data fields onto grid.


fbGrid = fbTabPage1.addControl(fctGrid,"Table Grid");
fbGrid.addDataField(fbDS.id(),
dictTable.fieldName2Id("AccountNum"));
fbGrid.addDataField(fbDS.id(),
dictTable.fieldName2Id("CustGroup"));
fbGrid.addDataField(fbDS.id(),
dictTable.fieldName2Id("Currency"));

// Add string fields to tab 2, and assign datasource


fbStr1 = fbTabPage2.addControl(fctString,"String 1");
fbStr1.dataSource(fbDS.id());
fbStr1.dataField(dictTable.fieldName2Id("AccountNum"));

fbStr2 = fbTabPage2.addControl(fctString,"String 2");


fbStr2.dataSource(fbDS.id());
fbStr2.dataField(dictTable.fieldName2Id("CustGroup"));

// Create the run-time form.


args = new Args();
args.object(form);
formRun = classfactory.formRunClass(args);
formRun.run();
formRun.detach();
}

Query Classes
Query classes were introduced in "Chapter 2: Working with Data." This lesson
provides a demonstration using these classes.

This example demonstrates how to use the Query classes to build a query.

1. Add a class to the AOT called QueryClassDemo.


2. Add a class method called createQueryRun().
3. Create a new Query object.
4. Add a new datasource for CustTable using Query.addDataSource().
5. Add a new range on CustTable.AccountNum using
QueryBuildDataSource.addRange().
6. Set a criteria on the range using QueryBuildRange.value().
7. Make the query sort by CustTable.CustGroup, using
QueryBuildDataSource.addOrderByField().

3-17
Development III in Microsoft Dynamics® AX 2012

8. Create a QueryRun object based on the query.


9. Return the QueryRun object.

public QueryRun createQueryRun()


{
Query query;
QueryRun qr;
QueryBuildDataSource qbds;
QueryBuildRange qbr;

query = new Query();

qbds = query.addDataSource(TableNum(CustTable));

qbr = qbds.addRange(FieldNum(CustTable, AccountNum));


qbr.value('4005');

qbds.addOrderByField(FieldNum(CustTable, CustGroup));

qr = new QueryRun(query);

return qr;
}

3-18
Chapter 3: Classes

Lab 3.2 - Create a Query From Code


Scenario

You have been asked to create a function that will quickly show the user a list of
items that have been sold on a sales order. This is a key business indicator for
your company.

Challenge Yourself!
Create methods that build and use a query to display data. It should prompt the
user for the sales order number and then list all item id's and item names of lines
on that sales order.

Step by Step

1. In the AOT create a new class called SalesListItems.


2. Add the following code to the classDeclartion and to four new
methods called buildQuery, listItems, run and main.
3. Save your changes to the class.
4. Right-click on the class and select Open.
5. Verify the results are what you would expect.

class SalesListItems
{
QueryRun queryRun;
}

private void buildQuery()


{
Query query;
QueryBuildDataSource dataSource;

query = new query();

dataSource = query.addDataSource(tableNum(SalesLine));
query.addQueryFilter(dataSource, fieldStr(SalesLine,
SalesId));

queryRun = new QueryRun(query);


}

private void listItems()


{
SalesLine salesLine;

while (queryRun.next())
{
salesLine = queryRun.get(tableNum(SalesLine));

3-19
Development III in Microsoft Dynamics® AX 2012

info(strFmt("%1 %2",salesLine.ItemId,
salesLine.itemName()));
}
}

private void run()


{
this.buildQuery();

if (queryRun.prompt())
this.listItems();
}

static void main(Args _args)


{
SalesListItems salesListItems = new SalesListItems();
salesListItems.run();
}

3-20
Chapter 3: Classes

Application Substituted Kernel Classes


Application substituted kernel classes extend system classes whose names begin
with "x". They are listed at the bottom of the Classes node in the AOT. To
indicate that the classes are special, their icons differ from those of the other
application classes.

Most application substituted kernel classes have a global variable used to access
the class. The global variables do not need a variable declaration and are pre-
instantiated by the client.

Each class serves a different purpose. The extent to which you will need to use
them vary.

When using the classes:

• Always use the application classes rather than using the system
classes directly.
• Use the global variables to access the classes, when available.
• Never instantiate these classes (except for the Session class). Use the
global variables listed below instead.
• Specify the application class name to access static methods.

Application class System class Global variable Runs on


Session xSession (none) Client and server
Application xApplication appl Server
Company xCompany appl.company Server
Info xInfo Infolog Client
ClassFactory xClassFactory classFactory Client and server
Global xGlobal (none) Client and server
VersionControl xVersionControl versionControl Client

Five of these classes are described further.

xApplication
The xApplication class provides access to information about the current
application. It also provides some application level functions.

The Application class extends the xApplication class, and allows its methods to
be overridden.

3-21
Development III in Microsoft Dynamics® AX 2012

Some of the more commonly used and overridden methods on Application are:

• buildNo() - returns the build number of the application in its current


state.
• releaseVersion() - returns the release version of the application.
• company([dataAreaId]) - gets or sets the dataArea that the user is
currently using.
• deleteCompany(dataAreaId) - deletes a company account indicated
by the dataAreaId.

xCompany
The xCompany class provides access to information about the current company
(dataArea). It also provides some application level functions.

The Company class extends the xCompany class, and allows its methods to be
overridden.

Some of the more commonly used and overridden methods on Company are:

• new() - this is called when a user changes company in their client.

xInfo
The xInfo class provides access to the InfoLog framework.

The Info class extends the xInfo class, and allows its methods to be overridden.

Some of the more commonly used and overridden methods on Info are:

• add() - adds a message to the infolog, to display it to the user.


• currentAOLayer() - returns the current layer the user is working in.
• createWorkspaceWindow()- opens a new workspace window.
• createDevelopmentWorkspaceWindow() - opens a new
development workspace window.
• infologData() - returns an InfoLogData container object holding the
messages currently in the infolog (which could be saved or displayed
somewhere other than the infolog, for example, in a log).

xGlobal
The xGlobal class provides a large selection of functions, designed to be used
anywhere in the application.

The Global class extends the xGlobal class, and allows its methods to be
overridden, and for extra global methods to be created. Many of the methods on
Global provide useful data manipulation functions. (For example,
numeralsToTxt(), str2con(), utcDateTime2SystemDateTime()).

3-22
Chapter 3: Classes

The static methods on Global are special in that they can be called from any
place in the application, without having to specify the Global class. For example,
Global::time2StrHMS() can be called as time2StrHMS(). In this way, they
become more like the system functions.

xClassFactory
The xClassFactory class is used by the system to create objects from classes.
One analogy of this is, a Class is a blueprint, xClassFactory is a factory and an
Object is the finished product.

The ClassFactory class extends the xClassFactory class, and allows its methods
to be overridden.

Some of the more commonly used methods on ClassFactory are:

• formRunClass(args) - creates a FormRun object for a specific


form design, using the formstr value in the args parameter.
• createClass(classId) - creates an object from a class Id.
• queryRunClass(args) - creates a QueryRun object for a specific
query design, using the querystr value in the args parameter.

The following example demonstrates how to use ClassFactory to create and run
the CustTable form using X++:

1. Create a new Args object, passing in formStr(CustTable).


2. Call ClassFactory.formRunClass() passing in the args object, and
placing returned object into a FormRun variable.
3. Call the init(), run() and detach() methods on the FormRun object.

Args args;
FormRun formRun;

args = new Args(formstr(CustTable));


formRun = classFactory.formRunClass(args);
formRun.init();
formRun.run();
formRun.detach();

3-23
Development III in Microsoft Dynamics® AX 2012

Lab 3.3 - Create a Global method


Scenario

Since your company's recent upgrade to Microsoft Dynamics AX 2012, there has
been a request for a function to convert a Date type value to an equivalent
utcDateTime type value. You have been given the task of creating a new function
to perform this conversion.

Challenge Yourself!
Create a global method which converts a date value to its equivalent
utcDateTime value. Write a method to accept a date value and set the time
component equal to midnight. Use the new global method to output the
utcDateTime of a specified date to the screen using a job.

Step by Step

1. Open the AOT.


2. Find the class called Global.
3. Right-click Global class and select New > Method.
4. Copy the code shown below for the method date2utcDateTime in to
the new method.
5. Save your changes to the Global class.
6. Create a new job.
7. Copy the code below for JobDate2UTC to the new job.
8. Press F5 to run the job and see the results.

public static utcDateTime date2utcDateTime(date _date)


{
return DateTimeUtil::newDateTime(_date, 0);
}

static void JobDate2UTC(Args _args)


{
print date2utcDateTime(mkDate(28,9,12));
pause;
}

3-24
Chapter 3: Classes

RunBase Framework
The functions in Microsoft Dynamics AX can be categorized into three basic
groups:

• Data entry/queries
• Reporting
• Data manipulation

Data entry/queries are implemented by developing forms, reporting is


implemented by reports and data manipulation is implemented by classes.

The RunBase class is an abstract class which defines a common structure for all
data manipulation functions in Microsoft Dynamics AX. Using this framework
has the following advantages:

• The framework ensures that all updates are structured equally.


• Sharing common code through inheritance.
• A common dialog layout and functions for all updates.
• Automatic "memory" of the user selections from last run.
• Easy implementation of batch execution (when using child class,
RunBaseBatch).
• Built-in support for common tasks like query criteria selection and
progress bar.

The following task is an example of data manipulation:

• The user is prompted to enter a customer account and a period


specified by a from date and a to date.
• The data manipulation sums up all customer transactions specified
by the account and period.
• The sum is presented in the infolog.

NOTE: The Business Operation Framework (BOF) is a new way to build


operations and take advantage of services and CIL execution. RunBase continues
to be a supported pattern for Microsoft Dynamics AX 2012.

When building new operations that need to be used interactively and from batch,
the BOF should be used.

More information about the BOF can be found here:

What's New: Services & AIF for Developers in Microsoft Dynamics AX 2012 -
http://go.microsoft.com/fwlink/?LinkID=224714&clcid=0x409

3-25
Development III in Microsoft Dynamics® AX 2012

Business Operations Framework (BOF) -


http://go.microsoft.com/fwlink/?LinkID=224713&clcid=0x409

The Minimum RunBase Implementation


The minimum implementation of a RunBase class consists of the following
components:

• ClassDeclaration
• pack
• unpack
• run
• description (static)
• main (static)

The following dialog methods are not explicitly required in a minimum


implementation, but we will explore them as part of this example:

• dialog
• getFromDialog

If the dialog methods are not overridden, this provides a standard blank dialog.

ClassDeclaration
The classDeclaration consists of three types of definitions:

• Variables used to create fields in the dialog.


• Variables used within the data manipulation .
• Local macro to define which variables to pack (in other words,
remember for next time, and/or use on the batch server).

In this example the class declaration is as follows.

public class DemoRunBase extends RunBase


{
DialogField dialogAccount;
DialogField dialogFromDate;
DialogField dialogToDate;

CustAccount custAccount;
FromDate fromDate;
ToDate toDate;

#DEFINE.CurrentVersion(1)
#LOCALMACRO.CurrentList
custAccount,

3-26
Chapter 3: Classes

fromDate,
toDate
#ENDMACRO
}

The individual fields of the dialog will be initialized in the method dialog().
When the dialog is accepted by the user, the contents of the dialog fields are read
in the method getFromDialog(). As both methods have to access the same
variables, they are defined as members of the class.

The manipulation in the method run() uses the information from the dialog. This
information is stored in variables with specific data types. These variables are
initialized in the method getFromDialog() and read in the method run().

The information for the last used dialog values is packed into a container
structure. The variables that should be packed are defined in the CurrentList
macro in the classDeclaration.

Dialog
This method builds a dialog and initializes the fields that will be used to capture
data from the user. These variables can be automatically initialized with the same
data selected in the last run.

Typically, developers initialize the dialog object by a call to super() and then add
fields and other controls, afterward. The dialog() method for this example has the
following contents.

protected Object dialog()


{
DialogRunBase dialog;
DialogGroup groupPeriod;

dialog = super();

dialogAccount =
dialog.addFieldValue(extendedTypeStr(CustAccount),
custAccount);

groupPeriod = dialog.addGroup("Period");

dialogFromDate =
dialog.addFieldValue(extendedTypeStr(FromDate), fromDate,
"Period from");

dialogToDate =
dialog.addFieldValue(extendedTypeStr(ToDate), toDate,
"Period to");

return dialog;

3-27
Development III in Microsoft Dynamics® AX 2012

Visual elements are added to the dialog design using the add*() methods on the
Dialog object. This code first adds one field of type CustAccount, and references
the custAccount variable (the value of which will be used as the default for the
field, if it has a value). Then, a group is added to the design, with a caption
"Period". The two fields added after that will be included in the just created
group (by default). These two fields also reference class variables, and include a
third text parameter to override the label on the fields.

When the dialog is rendered, it will appear as follows.

FIGURE 3.1 DIALOG FROM RUNBASE

GetFromDialog
This method is called immediately after the dialog is accepted by the user, and
before the run() method is called. It is used to transfer the information from the
dialog fields into the class variables.

The method has the following content.

public boolean getFromDialog()


{
boolean ret;

ret = super();

custAccount = dialogAccount.value();
fromDate = dialogFromDate.value();
toDate = dialogToDate.value();

return ret;
}

3-28
Chapter 3: Classes

Pack
The task for this method is to return a container with the information. This can be
used to initialize a similar data manipulation on another computer and/or at
another time. The information packed should be sufficient to communicate
information from the dialog to the data manipulation in the run-method.

The container should contain a version number as the first element. This number
should control how the rest of the container is structured. If you change the
contents of the container, you should increment the version number.

The pack method frequently has the following basic contents. However, it cannot
be inherited as it contains references to macros defined locally in the class
declaration.

public container pack()


{
return [#CurrentVersion,#CurrentList];
}

The pack/unpack method is used in the following situations:

• To save and restore the dialog between each use of the class. The
information is usually saved on a per user/company basis.
• To save and restore the specification of the manipulation to execute
on a batch server.
• To transfer the object from the client to the server. This is done to
optimize both the user dialog and the data manipulation in an AOS
environment.

Unpack
The unpack() method is the counterpart to the pack() method. It receives a
container as a parameter and restores the type specific variables of the class. The
method returns a Boolean with the value true if the information could be restored.

The unpack() method handles the current version number as a minimum, which
is defined in the ClassDeclaration. You can select to support unpacking of older
versions by extending the switch statement.

The unpack() method has the following content.

public boolean unpack(container _packedClass)


{
Version version = RunBase::getVersion(_packedClass);

switch (version)
{
case(#CurrentVersion) :

3-29
Development III in Microsoft Dynamics® AX 2012

[version,#CurrentList] = _packedClass;
break;
default :
return false;
}
return true;
}

Run
The run() method controls the data manipulation. The method can use the
variables defined in the classDeclaration and initialized from the dialog. The
method does not receive any formal parameters.

The content of the run() method is unique for each data manipulation, based on
requirements. It will typically contain some local variables and a "try-catch"
statement with TTS calls that wrap all physical data manipulation into one logical
transaction.

The method for our example has the following content.

public void run()


{
CustTrans custTrans;

select sum(AmountMST) from custTrans


where custTrans.AccountNum == ledgerAccount
&& custTrans.TransDate >= fromDate
&& custTrans.TransDate <= toDate;

info(strFmt("Sum equals %1", custTrans.AmountMST));


}

Description
The description() method returns a descriptive name for the data manipulation.
The value is used to identify the job in the batch queue and is used as the caption
in the dialog.

The method for this example is as follows.

static client server ClassDescription description()


{
return "Sum customer transactions";
}

3-30
Chapter 3: Classes

This method is static, which means that it can be executed on the opposite tier of
the object. In this case, because the method does not contain user interface or
data interface, the specification of both client and server results in an execution
of the method on the tier it is called from regardless of the settings on the class
properties.

Main
The main() method is the entry point when the class is executed from a menu
item. The method is static. It defines and initializes the object. Notice that new()
should never accept any parameters. The main method receives one formal
parameter that is an args() object. This object is described further.

The main() method is also responsible for calling prompt(), which executes the
dialog, and then calling run() to perform the manipulation.

static void main(Args _args)


{
DemoRunBase demoRunBase;

demoRunBase = new DemoRunBase();

if (demoRunBase.prompt())
{
demoRunBase.run();
}
}

With a main() method in place, a RunBase class can now be called from a menu
item in the AOT.

Execution Without Dialog


The RunBase framework is designed to implement classes which are executed by
menu items. It is also easy to execute the methods directly from X++. To
manually control the input with code, instead of using the dialog interface,
supplement the class with methods to initialize the variables without user
interaction.

If the previous example is changed to initialize, specify input for, and execute the
data manipulation without a dialog, it will require extra methods to set the type
specific variables.

3-31
Development III in Microsoft Dynamics® AX 2012

The following parm method can get or set the custAccount variable:

public CustAccount parmCustAccount(CustAccount _custAccount


= custAccount)
{
custAccount = _custAccount;
return custAccount;
}

Parm methods would also be required for the toDate and fromDate variables.

The following illustrates how you can execute the class directly from X++,
instead of from a menu item.

static void DemoRunBaseJob(Args _args)


{
CustAccount custAccount;
FromDate fromDate;
ToDate toDate;
DemoRunBase demoRunBase;

custAccount = "4001";
fromDate = mkdate(1,1,2006);
toDate = mkdate(31,12,2006);

demoRunBase = new DemoRunBase();

demoRunBase.parmCustAccount(custAccount);
demoRunBase.parmFromDate(fromDate);
demoRunBase.parmToDate(toDate);

demoRunBase.run();
}

This approach can also be used to transfer information from the main() method
to the object. If you combine a dialog with the above approach, call the method
getLast() on the RunBase object before activating the parm*() methods.
Otherwise, the prompt() method restores the choices from the last execution and
overwrites the initialization.

Integration of a Query
To incorporate a query into the dialog of a RunBase class, implement the
following methods and modifications. The following examples will replace the
CustAccount, FromDate and ToDate dialog fields, with a single query on the
CustTrans table, to allow the user to specify their own criteria.

3-32
Chapter 3: Classes

classDeclaration

The classDeclaration needs some additional variables defined to incorporate a


query. Define a class variable of the type QueryRun. This variable holds a
handle to the object which executes the query.

The classDeclaration now resembles the following content.

public class DemoRunBaseQuery extends RunBase


{
QueryRun queryRun;

#DEFINE.CurrentVersion(1)
#LOCALMACRO.CurrentList
#ENDMACRO
}

InitParmDefault
The RunBase framework calls the initParmDefault() method when initializing a
new object of a RunBase class, if the class does not have previous initialization
data stored in Usage data.

The following content shows you how to build a query dynamically.

public void initParmDefault()


{
Query query;
QueryBuildDataSource dataSource;

super();

query = new Query();

dataSource = query.addDataSource(tableNum(CustTrans));

dataSource.addRange(fieldNum(CustTrans, AccountNum));
dataSource.addRange(fieldNum(CustTrans, TransDate));

queryRun = new QueryRun(query);


}

If a class has data in Usage data, new objects are initialized with the unpack
method.

3-33
Development III in Microsoft Dynamics® AX 2012

QueryRun
The queryRun() method returns the handle to the queryRun. The method usually
resembles the following content.

public QueryRun queryRun()


{
return queryRun;
}

ShowQueryValues
The showQueryValues() method indicates if the query should be active in the
dialog. If the user must access the query in the dialog, override this method and
return true.

public boolean showQueryValues()


{
return true;
}

This method should now include the information stored in the queryRun object.
Do this by adding the result of the queryRun.pack() method to the container:

public container pack()


{
return [#CurrentVersion, queryRun.pack()];
}

When combining dialog fields with a query, the first container still contains the
#CurrentList macro:

public container pack()


{
return [#CurrentVersion, #CurrentList,
queryRun.pack()];
}

The unpack() method re-initializes the queryRun object from the saved
information. Do this as shown by the following content.

public boolean unpack(container _packedClass)


{
Version version = RunBase::getVersion(_packedClass);
container packedQuery;

switch (version)
{

3-34
Chapter 3: Classes

case #CurrentVersion:
[version, packedQuery] = _packedClass;
if (packedQuery)
{
queryRun = new QueryRun(packedQuery);
}
break;

default :
return false;
}
return true;
}

When combining dialog fields with a query, the unpack of the container contains
the #CurrentList macro.

[version, #CurrentList, packedQuery] = _packedClass;

Run
The run() method can use the queryRun object directly as it is initialized after
the user has accepted the dialog.

The run() method is implemented as shown in the following content.

public void run()


{
AmountMST amountMST;
CustTrans custTrans;

while (queryRun.next())
{
custTrans = queryRun.get(tableNum(CustTrans));

amountMST += custTrans.AmountMST;
}

info(strFmt("Sum equals %1", amountMST));


}

3-35
Development III in Microsoft Dynamics® AX 2012

When this version of the class is run from a menu item, the dialog has the query
select button and criteria preview, as shown in the following figure:

FIGURE 3.2 DIALOG WITH QUERY

3-36
Chapter 3: Classes

Lab 3.4 - Make a RunBase Class


Scenario

You have been asked to create a function that will quickly calculate the average
sales price of an item, between a range of dates. This is a key business indicator
for your company.

Challenge Yourself!
Make a new class called SalesCalcAverageSalesPrice, using the RunBase
framework. The class should prompt for an Item Number, and To and From
dates. The run() method should calculate the average sales price of all sales lines
for that Item, with order creation dates between the dialog To and From dates.
Put the result in the infolog.

Step by Step

1. In the AOT create a new class called SalesCalcAverageSalesPrice.


2. Add the following code to the classDeclartion
3. Override the methods run, pack, unpack, dialog and getFromDialog
and enter the code shown below.
4. Create a new method called main and enter the code shown below.
5. Save your changes to the class.
6. Right-click on the class and select Open.
7. Enter from and to dates in the dialog box and enter an item id.
8. Verify the results are what you would expect.

class SalesCalcAverageSalesPrice extends RunBase


{
DialogField dlgFromDate, dlgToDate, dlgItemId;
FromDate fromDate;
ToDate toDate;
ItemId itemId;

protected Object dialog()


{
Dialog dlg = super();

dlgFromDate = dlg.addField(identifierStr(FromDate));
dlgToDate = dlg.addField(identifierStr(ToDate));
dlgItemId = dlg.addField(identifierStr(itemId));

return dlg;
}

3-37
Development III in Microsoft Dynamics® AX 2012

public boolean getFromDialog()


{
boolean ret;

fromDate = dlgFromDate.value();
toDate = dlgToDate.value();
itemId = dlgItemId.value();

ret = super();

return ret;
}

public container pack()


{
return conNull();
}

public boolean unpack(container packedClass)


{
return true;
}

public void run()


{
CustInvoiceTrans custInvoiceTrans;

select sum(SalesPrice), sum(Qty) from custInvoiceTrans


where custInvoiceTrans.ItemId == ItemId
&& custInvoiceTrans.InvoiceDate >= fromDate
&& custInvoiceTrans.InvoiceDate <= ToDate;

if (custInvoiceTrans.Qty)
info(strFmt("Average sales price is %1",
custInvoiceTrans.SalesPrice / custInvoiceTrans.Qty));
else
info("No sales found");

static void main(Args _args)


{
SalesCalcAverageSalesPrice SalesCalcAverageSalesPrice
= new SalesCalcAverageSalesPrice();

if (SalesCalcAverageSalesPrice.prompt())
SalesCalcAverageSalesPrice.run();
}

3-38
Chapter 3: Classes

Args Object
The Args class defines information communicated between running application
objects. The application object that initiates the communication (the caller),
initializes an Args object containing some information and then passes it to the
application object that is being called.

Information transferred by the args object includes the following:

Method Data Type Information


record Common A handle to the record that the caller is
currently using. This information is
typically used to access the values of the
caller's record.
parmEnumType Int The Id of the enum type that is specified
on the parmEnum.
parmEnum AnyType An enumerated value.
parmObject Object A handle to an object that the caller wants
to transfer to the called object. This
option can be used to transfer any object.
parm Str A string which can be used to transfer
miscellaneous information. It is best
practice not to use this method. Try to
build the communication on the other
parm methods available.

Communication through the args object can occur automatically without any
X++ programming.

If the caller is activating the called object by a menu item the Args object is
automatically initialized and sent as a parameter to the object called. AOT
properties of the menu item will be used.

If the called object is a form or a report, it can automatically apply to the


information send by the Args object. Forms will automatically try to make a
delayed synchronize with the args.record().

However, you can program one or both sides of the communication yourself.

3-39
Development III in Microsoft Dynamics® AX 2012

The following is an example of code on the caller side


(\Classes\LedgerConsolidate\dialog).

Object dialog()
{
Args args;
FormRun formRun;

mayBeExecuted = false;
args = new Args();
args.name(formStr(LedgerConsolidate));
args.caller(this);

formRun = ClassFactory::formRunClassOnClient(args);
formRun.init();

return formRun;
}

This code shows an example of how to replace a dialog object with a form from
the AOT, within the RunBase framework. The form called can access the
LedgerConsolidate class through args.caller(). This way, the form and class can
exchange information.

The following is an example of programming the called side


(Classes\BankChequeCancel\main).

public static void main(Args args)


{
BankChequeCancel chequeCancel;
Args localArgs = new Args();
;

if (args.record())
{
switch (args.dataset())
{
case tablenum(BankChequeTable) :
chequeCancel =
BankChequeCancel::newBankChequeTable(args.record());
break;

default :
throw
error(strfmt("@SYS22828","@SYS22512"));
}

if (chequeCancel.prompt())
{
localArgs.caller(chequeCancel);
localArgs.record(args.record());

3-40
Chapter 3: Classes

BankChequeCancel::serverRun(localArgs);
}
}
}

Notice that this method also declares, initializes, and uses a local Args object for
calling the static method BankChequeCancel::serverRun().

3-41
Development III in Microsoft Dynamics® AX 2012

Lab 3.5 - Using Args


Scenario

You have been asked to add the average sales price calculation to the Released
products form. The current item id in the form should be used for the item id in
the calculation

Challenge Yourself!
Modify the class SalesCalcAverageSalesPrice so when it is called with an active
InventTable record, the class should set the item id based on this record and not
prompt the user for the item id.

Step by Step

1. In the AOT, find the SalesCalcAverageSalesPrice class.


2. Add a new method parmItemId as shown below.
3. Modify the main, dialog and getFromDialog methods as shown
below.
4. Drag the class to the Action node of the Menu Items node in the
AOT.
5. In the property sheet of the menu item just created, set the label to
Average sales price.
6. In another AOT, find the form
EcoResProductPerCompanyListPage.
7. Drag the menu item to the node Designs > Design > ActionPane >
ActionPaneTabSell > ButtonGroupSellOrders.
8. Saves your changes to the form.
9. In the application workspace navigate to Product information
management > Common > Released products.
10. Select item 1001 and click Sell > Average sales price.
11. Enter a From date and a To date and click OK.
12. The infolog will display the average sales price for that item. Click
OK to close the infolog.

private itemId parmItemId(itemId _itemId = itemId)


{
itemId = _itemId;
return itemId;
}

static void main(Args _args)


{
SalesCalcAverageSalesPrice SalesCalcAverageSalesPrice
= new SalesCalcAverageSalesPrice();
InventTable inventTable;

3-42
Chapter 3: Classes

if (_args.dataset() == tableNum(InventTable))
{
InventTable = _args.record();

SalesCalcAverageSalesPrice.parmItemId(InventTable.ItemId);
}

if (SalesCalcAverageSalesPrice.prompt())
SalesCalcAverageSalesPrice.run();
}

protected Object dialog()


{
Dialog dlg = super();

dlgFromDate = dlg.addField(identifierStr(FromDate));
dlgToDate = dlg.addField(identifierStr(ToDate));
if (!itemId)
dlgItemId = dlg.addField(identifierStr(itemId));

return dlg;
}

public boolean getFromDialog()


{
boolean ret;

fromDate = dlgFromDate.value();
toDate = dlgToDate.value();
if (!itemId)
itemId = dlgItemId.value();

ret = super();

return ret;
}

3-43
Development III in Microsoft Dynamics® AX 2012

Summary
Using system classes in conjunction with your modifications will help leverage
existing frameworks, and access important system data, to ultimately provide a
richer and more consistent Microsoft Dynamics AX enhancement for end users.

3-44
Chapter 3: Classes

Test Your Knowledge


Test your knowledge with the following questions.

1. Which of the following are Collection classes? (Select all that apply)
( ) Struct
( ) Map
( ) Container
( ) List

2. What class is used to iterate through the elements stored in a Map object?

3. RecordSortedList and RecordInsertList have some similarities but have


different uses. Which of the following is a major difference between the two
classes?
( ) RecordInsertList lacks the sort order features that are available in
RecordSortedList.
( ) RecordSortedList lacks the sort order features that are available in
RecordInsertList.
( ) RecordInsertList cannot insert multiple records into the database in
one trip.
( ) RecordSortedList cannot insert multiple records into the database in
one trip.

4. Match the following Application Object classes to their description:

_____ 1. Form a. This class contains property methods for


_____ 2. FormRun the properties on a form data source. Use
this class to manipulate the properties on a
_____ 3.
form data source by using X++ code.
FormBuildDesign
b. This is not a real class.
_____ 4.
FormBuildDataSource c. This class contains property methods for a
form tree control.
_____ 5.
FormBuildStringControl d. This is not a real class.

3-45
Development III in Microsoft Dynamics® AX 2012

_____ 6. e. This class contains property methods for


FormBuildTreeControl the properties on the Design node for a
_____ 7. form and methods for creating controls.
FormQueryDataSource f. This class contains methods for executing a
_____ 8. FormExecute form.
g. This class contains property methods for a
form string control.
h. This class contains property methods for
the form name and other form properties
and methods that enable addition of data
sources and controls to the form.

5. What does the ClassFactory.formRunClass() method do?

6. In which situations are the pack/unpack methods used in the RunBase


framework.

7. Which method on Args should only be used when no other methods will
suffice?
( ) parm()
( ) parmEnum()
( ) parmEnumType()
( ) record()

3-46
Chapter 3: Classes

Quick Interaction: Lessons Learned


Take a moment and write down three key points you have learned from this
chapter

1.

2.

3.

3-47
Development III in Microsoft Dynamics® AX 2012

Solutions
Test Your Knowledge
1. Which of the following are Collection classes? (Select all that apply)
(√) Struct
(√) Map
( ) Container
(√) List

2. What class is used to iterate through the elements stored in a Map object?

MODEL ANSWER:

MapEnumerator.
MapIterator is also correct, but a secondary choice.

3. RecordSortedList and RecordInsertList have some similarities but have


different uses. Which of the following is a major difference between the two
classes?
(•) RecordInsertList lacks the sort order features that are available in
RecordSortedList.
( ) RecordSortedList lacks the sort order features that are available in
RecordInsertList.
( ) RecordInsertList cannot insert multiple records into the database in
one trip.
( ) RecordSortedList cannot insert multiple records into the database in
one trip.

3-48
Chapter 3: Classes

4. Match the following Application Object classes to their description:

h 1. Form a. This class contains property methods for


f 2. FormRun the properties on a form data source. Use
this class to manipulate the properties on a
e 3. FormBuildDesign
form data source by using X++ code.
a 4.
b. This is not a real class.
FormBuildDataSource
c. This class contains property methods for a
g 5.
form tree control.
FormBuildStringControl
d. This is not a real class.
c 6.
FormBuildTreeControl e. This class contains property methods for
the properties on the Design node for a
b 7.
form and methods for creating controls.
FormQueryDataSource
f. This class contains methods for executing a
d 8. FormExecute
form.
g. This class contains property methods for a
form string control.
h. This class contains property methods for
the form name and other form properties
and methods that enable addition of data
sources and controls to the form.

5. What does the ClassFactory.formRunClass() method do?

MODEL ANSWER:

Given a reference to a form in the AOT, this method will return a FormRun
object, which is a runnable form object.

6. In which situations are the pack/unpack methods used in the RunBase


framework.

MODEL ANSWER:

To save and restore user input and to transfer the object from the client to the
server.

7. Which method on Args should only be used when no other methods will
suffice?
(•) parm()
( ) parmEnum()
( ) parmEnumType()
( ) record()

3-49
Development III in Microsoft Dynamics® AX 2012

3-50

You might also like