From Useful Job

X++: Create a list of child classes or find extended methods

Jet another small X++ job: This job shows a list of child classes per parent class.

If you want so see the parent-child relationship in AX2012, the best way is to Right click on the class > Add-ons > Type hierarchy browser.

find class relation

The Type hierarchy browser shows the relationships between parent and child classes as well as the methods in each class. While this is great functionality I use regularly, I found it a bit difficult to see which child classes inherit or override a particular parent method.

Type hierarchy browser

I wanted to add a parameter variable to the header of one of the invent movement child methods. Parent and child methods in X++ must have exactly the same parameters. This means if you want to add a parameter to a child method you must also add it to the parent method and to all the other children that over rides that particular method. The list below shows me how many child classes I need to modify if I want to add a parameter to one child method.

For example, the InventMovement class has 85 child classes. Only 24 of those children overrides the method “DefaultDimension”. If you want to pass an extra boolean parameter to any of those 24 “DefaultDimension” methods, you have to add it to the definition of all 24 methods and to the parent.

This job creates a list of the 24 classes that overrides the “DefaultDimension” method in the InventMovement class.

My first instinct was to use the SysDictClass’s hasObjectMethod method, but since the method is available on all children through inheritance, it always evaluate to true.

  1. if(childDictClass.hasObjectMethod(methodStr(InventMovement, DefaultDimension)))

Instead, you need to check if the method “DefaultDimension” is a method node under the class node in the AOT.

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void showExtendedMethods(Args _args)
  4. {
  5.     #define.ClassPath(@"\Classes\%1")
  6.  
  7.     SysDictClass    dictClass;
  8.     List            extendedClassList;
  9.     ListEnumerator  listEnumerator;
  10.     ClassName       className;
  11.     MethodName      methodName;
  12.     ClassId         childClass;
  13.     TreeNode        childClassNode;
  14.     TreeNode        methodNode;
  15.     int             numberofMethods;
  16.  
  17.     //set parent class and method you want to investigate
  18.     className           = "InventMovement";
  19.     methodName          = "DefaultDimension";
  20.  
  21.     dictClass           = new SysDictClass(className2Id(classname));
  22.     extendedClassList   = dictClass.extendedBy();
  23.  
  24.     //loop through all child classes
  25.     if (extendedClassList.elements())
  26.     {
  27.         listEnumerator = extendedClassList.getEnumerator();
  28.  
  29.         setprefix(strfmt("Class %1 extended by %2 classes.", className, int2str(extendedClassList.elements())));
  30.  
  31.         while (listEnumerator.moveNext())
  32.         {
  33.             childClass      = listEnumerator.current();
  34.             childClassNode  = TreeNode::findNode(strFmt(#ClassPath, classId2Name(childClass)));
  35.  
  36.             //Try to find a method node for the method you are looking for
  37.             methodNode  = childClassNode.AOTfindChild(methodName);
  38.  
  39.             if (methodNode)
  40.             {
  41.                 numberofMethods++;
  42.                 info(ClassId2Name(childClass));
  43.             }
  44.         }
  45.         info(strfmt("%1 child classes override the method '%2'.", numberofMethods, methodName));
  46.     }
  47. }

child class x++

List of table fields and types in CSV file

I wrote a job today to create a list of fields in a table and each field’s data type. You specify a table name in the variables, and it he job exports a list of non-system fields in the format: “Displayed name” (label), “Technical Name” and “Data Type” to CSV file.

Notice (line 50 – 70) that it lists the possible enum values for enum fields. It also adds the string length in brackets for string fields.

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void ListFields(Args _args)
  4. {
  5.     #File
  6.     DictField           dictField;
  7.     DictTable           dictTable;
  8.     DictType            dictType;
  9.     DictEnum            dictEnum;
  10.     FieldId             fieldId;
  11.     Types               type;
  12.     TableName           tableName = tableStr(BatchJob);
  13.  
  14.     str                 enumValues, stringLength, stringType;
  15.     int                 i;
  16.  
  17.     CommaTextIo         commaTextIo;
  18.     FileIOPermission    permission;
  19.     str                 fileName = strFmt(@"C:\%1.csv", tableName);
  20.     ;
  21.     permission = new FileIOPermission(fileName, #io_write);
  22.     permission.assert();
  23.  
  24.     commaTextIo = new CommaTextIo(fileName ,#io_write);
  25.     dictTable   = new dictTable(tableName2Id(tableName));
  26.     fieldId     = dictTable.fieldNext(0);
  27.  
  28.     if (fieldId)
  29.     {
  30.         commaTextIo.write("Displayed name", "Technical Name", "Data Type");
  31.     }
  32.  
  33.     while (fieldId)
  34.     {
  35.         dictField = new DictField(tableName2Id(tableName), fieldId);
  36.  
  37.         if (dictField && !dictField.isSystem())
  38.         {
  39.             type = dictField.baseType();
  40.  
  41.             switch (type)
  42.             {
  43.                 case Types::String:
  44.                     dictType        = new DictType(dictField.typeId());
  45.                     stringLength    = (strFmt("%1[%2]",type, dictType.displayLength()));
  46.  
  47.                     commaTextIo.write(dictField.label(), dictField.name(), stringLength);
  48.                     break;
  49.  
  50.                 case Types::Enum:
  51.                     dictEnum = new DictEnum(dictField.enumId());
  52.  
  53.                     for (i = 0; i < dictEnum.values(); i++)
  54.                     {
  55.                         if (i == 0)
  56.                         {
  57.                             enumValues = "Enumeration: ";
  58.                         }
  59.                         else
  60.                         {
  61.                             enumValues += "/";
  62.                         }
  63.  
  64.                         if (dictEnum.value2Name(i))
  65.                         {
  66.                             enumValues += dictEnum.value2Name(i);
  67.                         }
  68.                         else
  69.                         {
  70.                             enumValues += dictEnum.value2Name(i);
  71.                         }
  72.                     }
  73.  
  74.                     commaTextIo.write(dictField.label(), dictField.name(), enumValues);
  75.                     enumValues = '';
  76.                     break;
  77.  
  78.                 default:
  79.                     stringType = strFmt("%1", type);
  80.                     commaTextIo.write(dictField.label(), dictField.name(),  stringType);
  81.                     break;
  82.             }
  83.         }
  84.         fieldId = dictTable.fieldNext(fieldId);
  85.     }
  86.     CodeAccessPermission::revertAssert();
  87. }

The example above exports the fields for the “BatchJob” table. In AX 2012 R3, it generates this CSV file:

List of fields

X++ “Cross reference” for extended data types

I wrote a job to check which fields uses a specific enum. This week I needed something similar for an extended data type and I modified it to work with EDTs instead.

The cross reference on the environment I am currently working on is not up to date. I used this job to find all the table fields that use this extended data type, similar to what the standard cross reference function does.

Simply change the extended data type in line 5 to the EDT you want to investigate, and the job makes a list of table fields that uses the EDT. You can also change the layer depending on what you are looking for.

For example, for InterCompanyCompanyId, searching through all the fields in the SYS layer:

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void edtCrossReference(Args _args)
  4. {
  5.     str                 edtName = extendedTypeStr(InterCompanyCompanyId);
  6.     ExtendedTypeId      edtId = extendedTypeName2Id(edtName);
  7.     UtilIdElements      utilfield;     
  8.     DictField           dictField;       
  9.  
  10.     setPrefix(strFmt("EDT: %1", edtName));
  11.  
  12.     while select parentId, utilLevel, Name, id from utilfield
  13.         where utilfield.recordType == UtilElementType::TableField        
  14.         && utilfield.utilLevel  == UtilEntryLevel::sys
  15.     {
  16.         dictField = new DictField(utilfield.parentId, utilfield.Id);
  17.  
  18.         if (dictField.typeId() == edtId)
  19.         {      
  20.             info(strFmt("%1: %2 - %3", utilfield.utilLevel, tableId2name(utilfield.parentId), dictField.name()));
  21.         }
  22.     }
  23. }

IntercompanyCompanyId crossreferance

X++ “Cross reference” for Base Enums

While I love using the cross reference function in Dynamics AX 2012 (and am training myself to use it more often), the cross reference in our environments are often not up to date. Unfortunately this is especially true for our client development environments.

I wrote a job to find all the table fields that is based on a Base Enum. Simply change the enum name in line 5 to the enum you want to investigate, and the job makes a list of table fields that uses the enum. In my case I only needed tables in the ISV and up, but you can tweak it for all layers, or only one.

For example, for InterCompanyOrigin enum, searching through all the fields in the SYS layer:

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void enumCrossReference(Args _args)
  4. {
  5.     EnumName            enumName = enumStr(InterCompanyOrigin);
  6.     EnumId              enumId = enumName2Id(enumName);
  7.     UtilIdElements      utilfield;     
  8.     DictField           dictField;       
  9.  
  10.     setPrefix(strFmt("Enum: %1", enumName));
  11.  
  12.     while select parentId, utilLevel, Name, id from utilfield
  13.         where utilfield.recordType == UtilElementType::TableField        
  14.         && utilfield.utilLevel  == UtilEntryLevel::sys
  15.     {
  16.         dictField = new DictField(utilfield.parentId, utilfield.Id);
  17.  
  18.         if (dictField.baseType() == Types::Enum && dictField.enumId() == enumId)
  19.         {      
  20.             info(strFmt("%1: %2 - %3", utilfield.utilLevel, tableId2name(utilfield.parentId), dictField.name()));
  21.         }
  22.     }
  23. }
InterCompanyOrigin Enum
Output: InterCompanyOrigin Enum

It tends to run quite long, depending on how many fields and layers you include. I’ll see if I can find a way to speed it up in the future.

UPDATE 27 January 2016:
This job does the same for extended data types.

Generate a list of shared projects per layer

Today I add to compare the projects in an acceptance environment with the projects in the test environment. I did this so that I can make backups of the projects in test that are not in acceptance yet, before I import the acceptance model store.

Here in the simple job I wrote to show an list of shared projects in an infolog. You can change #ProjectSharedPath to see private projects or UtilEntryLevel::var enum so see projects in other layers.

  1. static void listOfSharedProjects(Args _args)
  2. {
  3.     #AOT
  4.  
  5.     Treenode        shared = TreeNode::findNode(#ProjectSharedPath);
  6.     Treenode        project;
  7.     UtilEntryLevel  utilEntryLevel = UtilEntryLevel::var;
  8.     Str             projectName;
  9.     int             nodeCount;
  10.     int             i;    
  11.  
  12.     nodeCount   = shared.AOTchildNodeCount();
  13.     project     = shared.AOTfirstChild();
  14.  
  15.     for (i=1; i<=nodeCount; ++i)
  16.     {
  17.         if (project.AOTLayer() == utilEntryLevel)
  18.         {
  19.             projectName = project.AOTname();        
  20.             info(projectName);
  21.         }
  22.         project = project.AOTnextSibling();
  23.     }
  24. }

Workbook.ExportAsFixedFormat in Microsoft Dynamics AX

How to export a file to PDF in Dynamics AX

Microsoft has an extremely useful .NET method named ExportAsFixedFormat() for exporting office files to PDF. For example, you can invoke the ExportAsFixedFormat() method on an Excel Workbook object and a PDF file is then created in the folder of your choice.

Unfortunately the documentation on how to use this in function in Dynamics AX is a little lacking. In the .NET documentation most of the parameters are described as optional, while in Dynamics AX you have to pass all the parameters.

The last parameter is a pointer to the FixedFormatExt class. It is not necessary to pass an actual value, but the method does not accept null. Saveen Reddy describes in this post how to pass an empty value in C#. In Dynamics AX is even more tricky, since you do not have access to System.Reflection.Missing.Value. You need to create an object like this:

  1.     System.Type                  type           = System.Type::GetType("System.Reflection.Missing");
  2.     System.Reflection.FieldInfo  info           = type.GetField("Value");
  3.     System.Object                missingVariant = info.GetValue(null);


The final job is below, notice:

  1. All the parameters are passed, none are optional (line 32)
  2. The last parameter is a ‘null’ object that uses System.Reflection.Missing (lines 20-22)
  3. The method is in a try and is followed by a catch for a CLRError. This makes debugging much easier since it displays the .NET error which would otherwise not have been shown (lines 42-54 )
  4. I have used System.Type since I was worried AX variables might confuse .NET. As far as I know you could use normal AX str and int variables (lines 13-18)

  1. static void ExcelExportPDFAsFixedFormat(Args _args)
  2. {
  3.     #File
  4.     Microsoft.Office.Interop.Excel.Application  xlApp;
  5.     Microsoft.Office.Interop.Excel.Workbooks    xlWBS;
  6.     Microsoft.Office.Interop.Excel.Workbook     xlWB;
  7.  
  8.     System.Exception                            exception;
  9.  
  10.     Filename                                    filename =  @'C:\PDF\test3.pdf';
  11.     Filename                                    excelFileName = @'C:\PDF\TR1-000058.xlsx';
  12.  
  13.     System.Int32                                toPage                  = 1;
  14.     System.Int32                                fromPage                = 1;
  15.     System.Boolean                              OpenAfterPublish        = false;
  16.     System.Boolean                              IncludeDocProperties    = true;
  17.     System.Boolean                              IgnorePrintAreas        = false;
  18.     System.String                               fileNameDotNet          = filename;
  19.  
  20.     System.Type                                 type = System.Type::GetType("System.Reflection.Missing");
  21.     System.Reflection.FieldInfo                 info = type.GetField("Value");
  22.     System.Object                               missingVariant = info.GetValue(null);
  23.  
  24.     new InteropPermission(InteropKind::ClrInterop).assert();
  25.  
  26.     xlApp = new Microsoft.Office.Interop.Excel.ApplicationClass();
  27.     xlWBs  = xlApp.get_Workbooks();
  28.     xlWB   = xlWBS.Add(excelFileName);
  29.  
  30.     try
  31.     {
  32.         xlWB.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType::xlTypePDF,
  33.                                  fileNameDotNet,
  34.                                  Microsoft.Office.Interop.Excel.XlFixedFormatQuality::xlQualityStandard,
  35.                                  IncludeDocProperties,
  36.                                  IgnorePrintAreas,
  37.                                  fromPage,
  38.                                  toPage,
  39.                                  OpenAfterPublish,
  40.                                  missingVariant);
  41.     }
  42.     catch(Exception::CLRError)
  43.     {
  44.         exception = ClrInterOp::getLastException();
  45.         while (exception)
  46.         {
  47.             error(exception.get_Message());
  48.             exception = exception.get_InnerException();
  49.         }
  50.     }
  51.     catch
  52.     {
  53.         error("An unknown exception has occurred");
  54.     }
  55.     CodeAccessPermission::revertAssert();
  56. }

It is also possible to create PDF’s form Word Documents. I’ll post an example if I write a job to do that in the future.

UPDATE 26 December 2014:
A senior colleague wanted to achieve something similar and I offered him this job. He pointed out that an instance of Excel is opened every time the job is run, but never closed. You can see this clearly in the Task Manager.

If you want to use this job, experiment with adding xlWB.Close and/or xlApp.Quit, and keep an eye op the running applications in the Task Manager.

How to generate a list of field names and field properties

Today I quickly wanted to see which fields in the PurchLine table are mandatory. Purchline is a large table, so instead of checking the properties of each field, I quickly wrote a job that displays an info log of all the mandatory fields. I used the DictClass example script from Microsoft, and adapted it so that it generates a list with all the fields in the table.

This job can be easily changed to display the fields of other tables, simply change the tableName variable. It can also be changed to display other properties like ‘Allows edit on create’. For this you need to change the #DBF_Mandatory flag to another flag defined in the macro:
DictField Macro

This script uses a number of interesting things like macros, dictField and dictTable. I’ll write separate posts for these topics soon. Until then, here is a simple jobs that lists all the fields in a table and their properties:

  1. static void getMandatoryFields(Args _args)
  2. {
  3.    #macrolib.dictfield  
  4.     DictField   dictField;
  5.     DictTable   dictTable;    
  6.     FieldId     fieldId;
  7.     int         nFlags;
  8.     TableName   tableName = "PurchLine";
  9.     ;
  10.     dictTable   = new dictTable(tableName2Id(tableName));
  11.     fieldId     = dictTable.fieldNext(0);    
  12.     while (fieldId)
  13.     {
  14.         dictField = new DictField(tableName2Id(tableName), fieldId);
  15.         if (dictField)
  16.         {
  17.             nFlags = dictField.flags();      
  18.             if (bitTest(nFlags, #DBF_MANDATORY))
  19.             {
  20.                 info(strFmt("The field %1 is mandatory.", dictField.name()));
  21.             }
  22.             else
  23.             {
  24.                 //info(strFmt("The field %1 is not mandatory.", DictField.name()));
  25.             }
  26.         }        
  27.         fieldId = dictTable.fieldNext(fieldId);
  28.     }
  29. }

The resulting info log should look something like this:
Mandatory purchline fields