3. Treenodes API
The
two reflection APIs discussed so far both have limitations. The table
data API can reflect only on the existence of elements and a small
subset of element metadata. The dictionary API can reflect in a
type-safe manner but only on the element types that are exposed through
this API.
The treenodes API can reflect on
everything, but as always, power comes at a cost. The tree-nodes API is
harder to use than the other reflection APIs discussed. It can cause
memory and performance problems, and it isn’t type-safe.
static void findInventoryClasses(Args _args) { TreeNode classesNode = TreeNode::findNode(@'\Classes'); TreeNodeIterator iterator = ClassesNode.AOTiterator(); TreeNode classNode = iterator.next(); ClassName className;
while (classNode) { className = classNode.treeNodeName(); if (strStartsWith(className, 'Invent')) info(strfmt("%1", className));
classNode = iterator.next(); } }
|
First, notice how you find a node in the AOT based on the path as a literal. The AOT
macro contains definitions for the primary AOT paths.
If
you stay at the class level in the AOT, you don’t encounter
problems—but be careful if you go any deeper. Tree nodes in MorphX
contain data that the Dynamics AX runtime doesn’t manage, and nodes’
memory isn’t automatically deallocated. For each parent node that is
expanded, you should call the TreenodeReleasedoTreeNode method on the SysBpCheck class. method to free the memory. For an example of this, see the
The following small job prints the source code for the doTreeNode method by calling the AOTgetSource method on the treenode object for the doTreeNode method.
static void printSourceCode(Args _args) { TreeNode treeNode = TreeNode::findNode(@'\Classes\SysBpCheck\doTreeNode'); ; info(treeNode.AOTgetSource()); }
|
The treenodes API provides access to the source code of nodes in the AOT. You can use the class ScannerClass to turn the string that contains the source code into a sequence of compilable tokens.
The following code revises the preceding example to find mandatory fields on the table CustTable.
static void mandatoryFieldsOnCustTable(Args _args) { TreeNode fieldsNode = TreeNode::findNode( @'\Data Dictionary\Tables\CustTable\Fields');
TreeNode field = fieldsNode.AOTfirstChild();
while (field) { if (field.AOTgetProperty('Mandatory') == 'Yes') info(field.treeNodeName());
field = field.AOTnextSibling(); } }
|
Notice
the alternate way of looping over subnodes. Both this and the iterator
approach work equally well. The only way to determine whether a field
is mandatory with this API is to know that your node models a field and
that field nodes have a property named Mandatory, which is set to Yes (not to True) for mandatory fields.
Use the Properties
macro when referring to property names. It contains text definitions
for all property names. By using this macro, you avoid using literal
names, as in the reference to Mandatory in the preceding example.
Unlike the dictionary API, which can’t reflect all elements, the treenodes API reflects everything. The SysDictMenu
class exploits this fact, providing a type-safe way to reflect on menus
and menu items by wrapping information provided by the treenodes API in
a type-safe API. The following job prints the structure of the MainMenu menu, which typically is shown in the navigation pane.
static void printMainMenu(Args _args) { void reportLevel(SysDictMenu _sysDictMenu) { SysMenuEnumerator enumerator;
if (_sysDictMenu.isMenuReference() || _sysDictMenu.isMenu()) { setPrefix(_sysDictMenu.label()); enumerator = _sysDictMenu.getEnumerator(); while (enumerator.moveNext()) reportLevel(enumerator.current()); } else info(_sysDictMenu.label()); }
reportLevel(SysDictMenu::newMainMenu()); }
|
Notice how the setPrefix function is used to capture the hierarchy and how the reportLevel function is called recursively.
The
treenodes API also allows you to reflect on forms and reports, as well
as their structure, properties, and methods. The Compare tool in MorphX
uses this API to compare any node with any other node. The SysTreeNode class contains a TreeNode class and implements a cascade of interfaces, which makes TreeNode classes consumable for the Compare tool and the Version Control tool. The SysTreeNode class also contains a powerful set of static methods.
The TreeNode class is actually the base class of a larger hierarchy. You can cast instances to specialized TreeNode
classes that provide more specific functionality. The hierarchy isn’t
fully consistent for all nodes. You can browse the hierarchy in the AOT
by clicking System Documentation, clicking Classes, right-clicking
TreeNode, pointing to Add-Ins, and then clicking Application Hierarchy.
The xUtil classes shown in the table data API examples contain methods for transitioning between the class paradigm of TreeNode classes and the table paradigm of UtilElements tables. Here is an example.
TreeNode node1 = TreeNode::findNode(@'\Data Dictionary\Tables\CustTable'); UtilElements utilElements = xUtilElements::findTreeNode(custTableNode); TreeNode node2 = xUtilElements::getNodeInTree(utilElements);
|
Although
we’ve covered only the reflection functionality of the treenodes API,
you can use the API just as you would use the AOT designer. You can
create new elements and modify properties and source code. The Wizard
Wizard uses the treenodes API to generate the project, form, and class
implementing the wizard functionality. You can also compile and get
layered nodes and nodes from the Old application folder (located in
Program Files\ Microsoft Dynamics
AX\5.0\Application\Appl\DynamicsAx1\Old). The capabilities that go
beyond reflection are very powerful, but proceed with great care.
Obtaining information in a non-type-safe manner requires caution, but
writing in a non-type-safe manner can lead to cataclysmic situations.