TreeNodeTraverser

Sometimes you need to get at objects within the AOT and identify what these are. One such case struck me where a CSV of Main Menu items had been manually typed. The idea was a review of the Main Menu and items Help Text, which sometimes can be overlooked with integration and lead to amongst other things, lack of status bar text.

This AOT Job should be run in a test or development environment and modified to suit your needs. It demonstrates the use of TreeNodeTraverser, which is similar yet different to TreeNodeIterator. The primary difference is that when Traversing, you need not worry about how many levels deep you may be in. It allows you to loop through each item and conduct fairly elaborate inspection of each items Property.

static void Menu_Structure_Helptext(Args _args) {   #AOT #Properties Boolean            writeFile; Boolean            infoDebug; CommaIo            outFile; str                errMsg; str                myClss; str                myProp; str                myNode; str                myText; str                myLabel; TreeNode           treeItem; TreeNode           treeMenu; TreeNode           treeMenuNode; TreeNodeTraverser  mainMenuTraverser; ;   infoDebug   =   true;   // ***OPTION*** View InfoLog Output         (true / false) writeFile  =   false;  // ***OPTION*** Export results to CSV file  (true / false)

errMsg = ""; //"--- NOT DEFINED ---"; myNode = "Administration";

outFile = new AsciiIo("C:\\MENUITEMS."+myNode+".CSV","W"); outFile.outFieldDelimiter("\r\n"); outFile.outRecordDelimiter("\r");

treeMenu         = TreeNode::findNode(#MenusPath + "\\" + myNode); mainMenuTraverser = new TreeNodeTraverser(treeMenu);

while (mainMenuTraverser.next) {       treeMenuNode = mainMenuTraverser.currentNode;

if (hasProperty(treeMenuNode.AOTgetProperties, #PropertyLabel)==true) {               myLabel = findProperty(treeMenuNode.AOTgetProperties, #PropertyHelpText); if(myLabel != "") {                   myText  = SysLabel::labelId2String(myLabel); if(infoDebug) { info(strfmt("%1 %2", treeMenuNode.treeNodeName, "      "+myText)); }                   if(writeFile) { outFile.write(treeMenuNode.treeNodeName + "  , " + strReplace(myText,",","")); }               }                else {                   myText = errMsg; if(infoDebug) { info(strfmt("%1 %2", treeMenuNode.treeNodeName, "      "+myText)); }                   if(writeFile) { outFile.write(treeMenuNode.treeNodeName + "  , " + strReplace(myText,",","")); }               }        }        else if(hasProperty(treeMenuNode.AOTgetProperties, #PropertyShortCut)==true) {           if(hasProperty(treeMenuNode.AOTgetProperties, #PropertyMenuItemType)) {               myLabel = findProperty(treeMenuNode.AOTgetProperties, #PropertyMenuItemType); myProp = findProperty(treeMenuNode.AOTgetProperties, #PropertyMenuItemName);

if(myLabel == "Display") { treeItem = TreeNode::findNode(#MenuItemsPath +"\\Display\\" + myProp); myClss = findProperty(treeItem.AOTgetProperties, #PropertyHelpText); if(myClss != "") { mytext = SysLabel::labelId2String(myClss); }                   else { myText = errMsg; }               }                else if(myLabel == "Action") { treeItem = TreeNode::findNode(#MenuItemsPath +"\\Action\\" + myProp); myClss = findProperty(treeItem.AOTgetProperties, #PropertyHelpText); if(myClss != "") { mytext = SysLabel::labelId2String(myClss); }                   else { myText = errMsg; }               }                else if(myLabel == "Output") { treeItem = TreeNode::findNode(#MenuItemsPath +"\\Output\\" + myProp); myClss = findProperty(treeItem.AOTgetProperties, #PropertyHelpText); if(myClss != "") { mytext = SysLabel::labelId2String(myClss); }                   else { myText = errMsg; }               }            }            if(infoDebug) { info(strfmt("%1 %2", treeMenuNode.treeNodeName, "      "+myText)); }           if(writeFile) { outFile.write(treeMenuNode.treeNodeName + "  , " + strReplace(myText,",","")); }       }        else { myText = errMsg; if(infoDebug) { info(strfmt("%1 %2", treeMenuNode.treeNodeName, "      "+myText)); }          if(writeFile) { outFile.write(treeMenuNode.treeNodeName + "  , " + strReplace(myText,",","")); }       }    } }

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.

Unlike the dictionary API, which cannot reflect all elements, everything can be reflected with the treenodes API. This fact is exploited in the SysDictMenu class, which provides 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 job below prints the structure of the MainMenu menu, or a specific MainMenu item. This variable is declared within the string "specific". As in the previous example the ability to output the result to a CSV file. Notice the useage of info and warning to work upon missing HelpText.

static void DisplayMainMenu(Args _args) {   AsciiIo        outFile; str           specific = "Ledger"; Boolean       allItems = true; Boolean       writeOut = false; identifiername idname  = specific;

void reportLevel(SysDictMenu _sysDictMenu) {       SysMenuEnumerator enumerator;

if (_sysDictMenu.isMenuReference || _sysDictMenu.isMenu) {           setPrefix(_sysDictMenu.label  + "     " + _sysDictMenu.helpText); enumerator = _sysDictMenu.getEnumerator;

while (enumerator.moveNext) { reportLevel(enumerator.current); }        }         else { if(writeOut) {               outFile.writeExp([strReplace(_sysDictMenu.label,",",""),strReplace(_sysDictMenu.helpText,",","")]); }           if(_sysDictMenu.helpText !="") { if(_sysDictMenu.helpText == _sysDictMenu.label) {                   warning(_sysDictMenu.label + "     " + _sysDictMenu.helpText); }               else {                   info(_sysDictMenu.label + "     " + _sysDictMenu.helpText); }           }            else { error(_sysDictMenu.label); }        }    }    if(writeOut) { outFile = new AsciiIo("C:\\Axapta.MenuItem.HelpText.csv","w"); outFile.outFieldDelimiter(","); outFile.outRecordDelimiter("\r"); outFile.writeExp(["Menu_Item","Help_Text"]); }   if(allItems) { /* NOTE: Main Menu and all decendants */ reportLevel(SysDictMenu::newMainMenu); }   else { /* NOTE: Specific Menu Item */ reportLevel(SysDictMenu::newMenuName(idname)); } }

Notice that the setPrefix function, captures the hierarchy and the reportLevel function is called in recursively. This is a very important lesson as in the previous example, setPrefix function will not work as intended.

MorphX uses the SysDictMenu API to compare any node with another node. The SysTreeNode class contains a TreeNode class and implements a cascade of interfaces. TreeNode classes become consumable for the Compare tool and the Version Control tool. The SysTreeNode class contains a powerful set of static methods, which can be extended with due diligence.

The TreeNode class is the base class of a larger hierarchy. You can cast instances to specialized TreeNode classes that provide more specific functionality. The hierarchy is not consistent for nodes. Browse the hierarchy in AOT by clicking System Documentation, clicking Classes, right-clicking TreeNode, pointing to Add-Ins, and then clicking Application Hierarchy.