InventDimDevelop macro is used to mark places in the application where specific inventory dimension(s) are referred from the code explicitly. The idea behind it is to help developers to find all the places that potentially have to be updated if a new inventory dimension is added.
For example, the following code should be marked with InventDimDevelop since size, color and configuration are referred explicitly:
#InventDimDevelop
this.ConfigId = inventDimCombination.ConfigId;
this.InventSizeId = inventDimCombination.InventSizeId;
this.InventColorId = inventDimCombination.InventColorId;
However, the following code processes all dimensions in a loop, so newly added dimension will be handled automatically. No need for InventDimDevelop.
container dimFields = InventDim::dimFieldList();
int h, y;
;
for (h = 1; h <= conlen(dimFields); h++)
{
y = conpeek(dimFields, h);
this.(y) = _inventDim.(y);
}
Wednesday, March 31, 2010
Tuesday, March 30, 2010
SysSpellChecker
There is a class in AX that allows to perform spell checking. This class uses Microsoft Word spell checker to do the validation. So, in order to use it, Microsoft Word should be installed together with proofing language pack. Because of this requirement it is also important to consider where to run spell checking - on the server or on the client.
Example:
public static server void spellCheckerTest()
{
SysSpellChecker sp = SysSpellChecker::newLanguageId('en-us');
;
info(strfmt('%1', sp.checkSpelling("behavior")));
info(strfmt('%1', sp.checkSpelling("behaviour")));
}
The output in infolog will be:
1
0
Upd: I've received a link to the interesting post where there is more information about AX spell checker - http://sangi1983.spaces.live.com/blog/cns!264A0056CBCBB1D3!404.entry?wa=wsignin1.0&sa=93421708
Example:
public static server void spellCheckerTest()
{
SysSpellChecker sp = SysSpellChecker::newLanguageId('en-us');
;
info(strfmt('%1', sp.checkSpelling("behavior")));
info(strfmt('%1', sp.checkSpelling("behaviour")));
}
The output in infolog will be:
1
0
Upd: I've received a link to the interesting post where there is more information about AX spell checker - http://sangi1983.spaces.live.com/blog/cns!264A0056CBCBB1D3!404.entry?wa=wsignin1.0&sa=93421708
Monday, March 29, 2010
Exporting and importing compiler output
I am probably not the only person who had faced the following problem – if you are doing some major change, like renaming of a table or a class you are compiling the AOT to get all the places where references to renamed object should be changed. The problem is that the resulting error log can be easily lost, for example, by pressing reset or closing AX by accident. Unfortunately in this case it cannot be restored easily, only with yet another AOT compilation. To avoid recompilation I found out for myself great functionality that allows to export and import compiler error logs. It is accessible from the Compiler output window->Import/Export.
It is also interesting fact that AX uses html format to store error logs.
It is also interesting fact that AX uses html format to store error logs.
Friday, March 26, 2010
Editor scripts
I’ve noticed that there are only two editor scripts that I am using during development. First one is XML documentation template (Scripts\Documentation\Header Template) – very nice script for documenting classes\methods. It automatically generates documentation sections for description, input and output parameters and exceptions. The second one is parm method template (Scripts\Template\Method\Parm) – creates a standard AX getter\setter. The rest of the scripts are either to specific and need for them doesn’t appear in my daily work or they are doing too simple things and it is faster for me just type them.
What editor scripts are you using from standard AX?
What known editor scripts extensions are you using?
What kind of custom editor scripts are you implementing for yourself?
What editor scripts are you using from standard AX?
What known editor scripts extensions are you using?
What kind of custom editor scripts are you implementing for yourself?
Thursday, March 25, 2010
Global::isType
The easiest way to determine if an EDT extends another EDT (not necessarily directly) is to use Global::isType() method.
For example:
isType(extendedtypenum(PurchUnit), extendedtypenum(UnitIDBase));
give true, since PurchUnit extends UnitID, which extends UnitIDBase.
isType(extendedtypenum(ABCModelType), extendedtypenum(NoYesId));
gives false, since ABCModelType and NoYesId are in different hierarchies.
For example:
isType(extendedtypenum(PurchUnit), extendedtypenum(UnitIDBase));
give true, since PurchUnit extends UnitID, which extends UnitIDBase.
isType(extendedtypenum(ABCModelType), extendedtypenum(NoYesId));
gives false, since ABCModelType and NoYesId are in different hierarchies.
Wednesday, March 24, 2010
DefaultAction property on grids
DefaultAction property on the form grid control was added in AX 2009. It allows to specify a menu item from the form which will be executed on the double click event on the grid. Originally this feature targets list pages – it allows to open details forms for the selected record on the list page. However, it can be used for any grid on any form. The only limitation is that grid should not be editable.
Tuesday, March 23, 2010
Union queries
In AX 2009 a great enhancement was introduced to the queries – support for union operation was added. A new property was added to the AOT queries – QueryType. It has two values:
In the case of union query several datasources of the same type (the same table) can be placed on the query datasource root node. Note, that all datasources except the first one will have a property called UnionType, which specifies what should be done with duplicated records that may appear because of union:
Additional datasources can be added to the deeper levels of the query under any datasource that participates in union. Those will be translated into joins. The only allowed join types for the union query are exists and notexists join.
- Join – regular query
- Union – union query
In the case of union query several datasources of the same type (the same table) can be placed on the query datasource root node. Note, that all datasources except the first one will have a property called UnionType, which specifies what should be done with duplicated records that may appear because of union:
- Union – remove duplicated records
- UnionAll – keep duplicated records
Additional datasources can be added to the deeper levels of the query under any datasource that participates in union. Those will be translated into joins. The only allowed join types for the union query are exists and notexists join.

Monday, March 22, 2010
Wizard wizard
There is a tool in AX that allows automatically generate code for new AX wizards. This tool is accessible from the Main menu\Tools\Development tools\Wizards\Wizard Wizard. By using this tool a new project with a wizard form, controlling class and a menu item will be generated. There will be also several methods created on the controlling class and on the form. Actually, the code generated by the tool is a good start for implementing a new wizard.
Friday, March 19, 2010
Query.pack method parameter
The comment from here forced me to pay attention to the Boolean parameter on the Query.pack() method which I didn't mention before. It is not obvious what it does by looking at its name - _doCheck. I tried to figure out what it does and I've got the following:
If a query contains DynaLink then calling Query.pack(true) will throw the error: Attempting to pack a query that contains one or more dynamic links.
If a query contains DynaLink then calling Query.pack(false) will succeed and DynaLink will not be stored in the package - it will not appear on a new query created from the package.
Does anybody have more information on this parameter?
Example:
public static void testQueryPack()
{
InventItemGroup itemGroup;
Query q1 = new Query();
Query q2;
QueryBuildDataSource qbds;
qbds = q1.addDataSource(tablenum(InventTable));
qbds.addDynalink(fieldnum(InventTable, ItemGroupId), itemGroup, fieldnum(InventItemGroup, ItemGroupId));
info(qbds.toString());
q2 = new Query(q1.pack(false));
qbds = q2.dataSourceTable(tablenum(InventTable));
info(qbds.toString());
}
Result:
If a query contains DynaLink then calling Query.pack(true) will throw the error: Attempting to pack a query that contains one or more dynamic links.
If a query contains DynaLink then calling Query.pack(false) will succeed and DynaLink will not be stored in the package - it will not appear on a new query created from the package.
Does anybody have more information on this parameter?
Example:
public static void testQueryPack()
{
InventItemGroup itemGroup;
Query q1 = new Query();
Query q2;
QueryBuildDataSource qbds;
qbds = q1.addDataSource(tablenum(InventTable));
qbds.addDynalink(fieldnum(InventTable, ItemGroupId), itemGroup, fieldnum(InventItemGroup, ItemGroupId));
info(qbds.toString());
q2 = new Query(q1.pack(false));
qbds = q2.dataSourceTable(tablenum(InventTable));
info(qbds.toString());
}
Result:

Thursday, March 18, 2010
Debug::assert
This method allows to check if some Boolean condition evaluates to true and if it doesn't - stop further execution and show stack trace. Debug::assert() should be used to validate some assumptions made in the code which should never happen if nothing is broken in the application. It means that wrong user input should not be asserted but rather validated by normal if statement and exception should be thrown if the input is wrong. However, if the code relies, for example, on query data source name, which can be broken only in development time - that is a good place to add assert to.
Example:
Query query = new Query(querystr(Alertsetup));
QueryBuildDataSource qbds;
;
qbds = query.dataSourceName(identifierstr(EventRule));
Debug::assert(qbds != null);
Example:
Query query = new Query(querystr(Alertsetup));
QueryBuildDataSource qbds;
;
qbds = query.dataSourceName(identifierstr(EventRule));
Debug::assert(qbds != null);
Wednesday, March 17, 2010
Instantiating query object
Constructor of the Query class has default anytype parameter named _source. To my knowledge there are four different ways to use it:
Is there anything else that can be used as a source for constructing a query?
- Instantiating an empty query – no parameter value should be specified:
Query q = new Query(); - Instantiating a query based on the AOT query:
Query q = new Query(querystr(Alertsetup)); - Instantiating a query as the copy of another query:
Query q1 = new Query();
Query q2 = new Query(q1); - Instantiating a query from the container with the packed query
Query q1 = new Query();
Query q2 = new Query(q1.pack());
Is there anything else that can be used as a source for constructing a query?
Tuesday, March 16, 2010
Global::curExt2dataareaId
While working with virtual companies it is needed sometimes to determine what is the actual DataAreaId that will be assigned to the table record if it will be inserted in the current company context. To do that there is the method curExt2dataareaid() on the Global class.
Example:
Let there are 2 companies A and B and the virtual company V that includes both A and B.
If table Unit is shared in the virtual company V and current company is A then
curExt2dataareaid(tablenum(Unit)) == 'V'.
If table Unit is not shared in the virtual company V and current company is A then
curExt2dataareaid(tablenum(Unit)) == 'A'.
Example:
Let there are 2 companies A and B and the virtual company V that includes both A and B.
If table Unit is shared in the virtual company V and current company is A then
curExt2dataareaid(tablenum(Unit)) == 'V'.
If table Unit is not shared in the virtual company V and current company is A then
curExt2dataareaid(tablenum(Unit)) == 'A'.
Monday, March 15, 2010
MenuItem types
What is the difference between MenuItem types - Display, Action and Output, except that they reside under different AOT nodes and that different intrinsic functions should be used to access them via reflection?
I know one - they are displayed with different images in the navigation pane:

Are there any other differences?
I know one - they are displayed with different images in the navigation pane:

Are there any other differences?
Friday, March 12, 2010
Cross-references for kernel classes
It is good to know that it is possible to view cross-references for kernel classes, tables, enums, etc. This can be done by using "System Documentation" node in AOT.
For example, to view cross-references for lookupField method of the Args class you can do the following:
1) Navigate to the "System Documentation\Classes\Args\lookupField" AOT node
2) Open context menu
3) Navigate Add-Ins\Cross-reference\Used by
For example, to view cross-references for lookupField method of the Args class you can do the following:
1) Navigate to the "System Documentation\Classes\Args\lookupField" AOT node
2) Open context menu
3) Navigate Add-Ins\Cross-reference\Used by
Thursday, March 11, 2010
SaveContents property on the table fields
I've opened for myself the new great property of the table fields - SaveContents. This property defines whether field will be physically created in the database (Yes) or will it just be present on the table buffer (No). It can be quite useful to have such kind of fields if you want to avoid redundancies in the database but you need to perform some complex calculations/queries and use their results on the table buffer. To get these fields filled with values you have to override the postLoad() method on the table.
For example, let you have the table:
Table1
IntField1 : Int, SaveContents = Yes
IntField2 : Int, SaveContents = Yes
IntField3 : Int, SaveContents = No
If you'll override postLoad() method on the Table1 like:
public void postLoad()
{
this.IntField3 = this.IntField1 + this.IntField2;
}
then you'll get the IntField3 field filled with the sum of the other two field values. The postLoad() method will be executed on each query on Table1 table. That is huge drawback because of serious performance degradation, however it still can be useful sometimes.
For example, let you have the table:
Table1
IntField1 : Int, SaveContents = Yes
IntField2 : Int, SaveContents = Yes
IntField3 : Int, SaveContents = No
If you'll override postLoad() method on the Table1 like:
public void postLoad()
{
this.IntField3 = this.IntField1 + this.IntField2;
}
then you'll get the IntField3 field filled with the sum of the other two field values. The postLoad() method will be executed on each query on Table1 table. That is huge drawback because of serious performance degradation, however it still can be useful sometimes.
Wednesday, March 10, 2010
Define overwrites
It is a little bit weird how X++ compiler handles the case when macros with the same name are defined in the class declaration of a class and in the one of its methods. The expected behavior for me is to either not allow to do that or to have a macro from class declaration to be applicable everywhere except for the method where it is redefined. But in AX the value defined in the method will overwrite the value defined in the class declaration and will be the same in all methods.
Example:
class TestDefine
{
#define.A(‘A’)
}
public void method1()
{
;
info(#A);
}
public void method2()
{
#define.A(‘B’)
;
info(#A);
}
If method1 or method2 will be called the ‘B’ will be received in the infolog and not ‘A’.
The next question will be what if define is overwritten in several methods. The answer is that the result is undetermined. It depends on X++ compiler internal flow and cannot be predicted. So, just avoid such constructions and even better avoid defines at all.
Example:
class TestDefine
{
#define.A(‘A’)
}
public void method1()
{
;
info(#A);
}
public void method2()
{
#define.A(‘B’)
;
info(#A);
}
If method1 or method2 will be called the ‘B’ will be received in the infolog and not ‘A’.
The next question will be what if define is overwritten in several methods. The answer is that the result is undetermined. It depends on X++ compiler internal flow and cannot be predicted. So, just avoid such constructions and even better avoid defines at all.
Tuesday, March 9, 2010
TableGroup property
The TableGroup property on tables not just categorizes them. I know two cases where this property has influence on the functionality:
Are there any other cases where this property affects functionality?
- Forceliterals keyword is added to the queries which contain two or more joined tables of group Miscellaneous, Transaction, WorksheetHeader or WorksheetLine.
- Record templates can be created only for the tables of Main or Group groups.
Are there any other cases where this property affects functionality?
Monday, March 8, 2010
Getting label text in different languages
Sometimes it is needed to get a label text in a language that is different from the current one. To do that the labelId2String method of the SysLabel class can be used.
Example:
SysLabel::labelId2String(literalstr('@SYS1'), 'en-us');
Gives: Time transactions
SysLabel::labelId2String(literalstr('@SYS1'), 'de');
Gives: Zeit Transaktionen
Example:
SysLabel::labelId2String(literalstr('@SYS1'), 'en-us');
Gives: Time transactions
SysLabel::labelId2String(literalstr('@SYS1'), 'de');
Gives: Zeit Transaktionen
Friday, March 5, 2010
xSession::xppCallStack method
This method allows to get the current call stack - all caller method names and line numbers. For example, calling xSession::xppCallStack() from the line 13 at the Tutorial_RunbaseBatch.dialog() method will give the following call stack:

Actually I even found one nice practical application for it - SysTestAssert class can be modified so that it will display the line number where assertion has failed. It becomes much easier to debug unit tests with such modification.

Actually I even found one nice practical application for it - SysTestAssert class can be modified so that it will display the line number where assertion has failed. It becomes much easier to debug unit tests with such modification.
Thursday, March 4, 2010
Global::queryValue method
This method translates anything into the correct query range value, so you don't have to care about how UTCDateTime value should be converted into a string or which symbols require slash before them. It is the best practice to always use this method when query range is assigned a value, even if the value is string.
Note, that queryValue('') will return the following string '""' (two double quotes) which will give empty string range value. If you want to clear the range you'll have to assign it's value with empty string directly.
Note, that queryValue('') will return the following string '""' (two double quotes) which will give empty string range value. If you want to clear the range you'll have to assign it's value with empty string directly.
Wednesday, March 3, 2010
System Documentation
It is known fact that in AX there are quite a lot of kernel classes, tables, data types, functions, etc. that are not visible in the AOT but can be used from the code. For example, class Args. There is a nice way to get an overview of all this stuff - in the System Documentation node in the AOT. The full list of all kernel objects and functions can be found there. And actually some of them have nice documentation - the one that can be found on MSDN.
Tuesday, March 2, 2010
Defining UTCDateTime constants
I was always using DateTimeUtil class to set a specific UTCDateTime value:
dateTime = DateTimeUtil::newDateTime(02\03\2010, 17*3600+30*60);
But it appeared that there is an easier way to define UTCDateTime constant:
dateTime = 2010-03-02T17:30:00;
dateTime = DateTimeUtil::newDateTime(02\03\2010, 17*3600+30*60);
But it appeared that there is an easier way to define UTCDateTime constant:
dateTime = 2010-03-02T17:30:00;
Monday, March 1, 2010
Global::exceptionTextFallThrough
This method looks really strange since it does nothing! However, this one is still quite useful. Sometimes there is a need to display text of the error that was thrown in the infolog but without preventing further code execution. For this purpose the following code can be written:
try
{
throw error("Error");
}
catch
{
}
The problem with this code is that compiler will generate a warning regarding empty compound statement (the one after catch). To avoid this warning exceptionTextFallThrough() method should be used.
try
{
throw error("Error");
}
catch
{
}
The problem with this code is that compiler will generate a warning regarding empty compound statement (the one after catch). To avoid this warning exceptionTextFallThrough() method should be used.
Subscribe to:
Posts (Atom)