A blog offering various X++ code snippets and tips for Microsoft Dynamics AX

Friday, October 31, 2008

Using a different query form

Each query that ships with standard Ax uses the standard query form (SysQueryForm) when interactive is set to 'Yes' (by default). Some customers would want a different query form rather than the standard form for a particular query (usually displayed when you press the 'select' button in a dialog). You can do this by creating your customer query form and changing the form property of the query in the AOT property-sheet. You can use the standard query form (SysQueryForm) as a basis for your new form by duplicating it and modifying it.

Remember: To make a query form non-modifiable, you can set the UserUpdate property to No.

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Wednesday, October 29, 2008

Displaying and Saving a Query

Sometimes it might be useful to display a query you have created in the AOT to the user. After this the user makes changes to the query (adds ranges and sort fields) and you would like to save the query (as changed by the user) to a table in the database, therefore saving the state of the query as modified by the user. Then you can re-use the query later by retreiving it from the table.

To do this you need to:

1. Create a field in your table of type container;
2. Create the query, you want to initially display to the user, as usual from the AOT;
3. Use the following code:


void createAndSaveQueryInTable()
{
  QueryRun queryRun;
  YourTable yourTable;
  ;
  // Attach new queryRun object to AOT Query
  queryRun = new QueryRun(queryStr('YourQuery'));

  // Display the query to the user for him/her to make the necessary changes
  if (queryRun.prompt())
  {
    // Save the packed query to the database
    // Pack the queryRun so that it can be saved in the table
    yourTable.QueryContainerField = queryRun.pack();
    yourTable.insert();
   }
}


Now, to retrieve the saved query from the table, for some other use, is equally simple:


void retrieveQueryFromTable()
{
  Container packedQuery;
  YourTable yourTable;
  ;
  // Get the necessary record from the table
  yourTable = YourTable::find(......);

  // Get the query stored in the table in packed form
  packedQuery = yourTable.QueryContainerField;

  // check if you already have a query packed in your field
  if (packedQuery)
  {
    // Unpack it into a queryRun
    queryRun = new QueryRun(packedQuery);
  }
  else
  {
    // Call method to create a new query - method shown in code snippet above
    queryRun = this.createAndSaveQueryInTable();
  }

  // Do any cool work with your QueryRun here
  // .....
}


If you any questions or comments e-mail me on: mirko@mirkobonello.com

Tuesday, October 28, 2008

Dynamics Ax 2009 RCT

Here is a link to the Dynamics Ax 2009 Rapid Configuration Tool which is available to download from PartnerSource:

Click Here

The key features of this tool are:

Best Practice documentation for building partner Knowledge Base;
AX 2009 Data Import and Export;
Project Management via Microsoft Project Integration and Issue Tracking;
Alerts and Notifications;
Fast and Easy AX Configuration;

From MBS website:

This latest version of the RCT provides a newly generated set of activities for easier implementation of Microsoft Dynamics® AX 2009. In addition, it provides significant improvements to activity dependencies as well as the ability to detect circular dependencies among activities. The tool includes updated and expanded Best Practices documentation for Microsoft Dynamics® AX2009. MS Project import and export functionality has been improved along with the Installer which has been improved giving better error detection by capturing logs.

The RCT for Microsoft Dynamics® AX 2009 is a standalone installation of the RCT version originally provided as part of the previous Partner Productivity Toolkit.

Note: The Rapid Configuration Tool for Microsoft Dynamics® AX2009 is released as a separate tool from the Microsoft Dynamics® AX application and is not supported by Microsoft.


If you any questions or comments e-mail me on: mirko@mirkobonello.com

Dynamics Ax 2009 Exams

It seems that some MS Dynamics Ax 2009 exams are in the pipeline:

AX 2009 Development Introduction - November 20 2008

AX 2009 Enterprise Portal - November 26 2008

Keep up to date with the latest exams from Microsoft here.

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Dynamics Ax 2009 Enterprise Portal Sample Code

To find some very good AX 2009 Enterprise Portal sample code in various formats:
Click Here

Some AX 2009 EP videos:
Click Here

A very good DAX 2009 EP blog:

Click Here


If I find some more AX 2009 useful resources I will append them to this post.

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Using an AOT query in a form at runtime

This is not the recommended/standard way to use queries in forms but in case you might need it - here is how it can be done:


public void init()
{
  QueryRun queryRun;
  Args args = new Args();
  ;
  super();

  args.name(querystr(MyAOTQuery));
  queryRun = classfactory.queryRunClass(args);
  this.query(queryRun.query());
}


In the above snippet, inserted in the init() method of the datasource, a QueryRun and an Args object was created. The args.name property was set to the AOT query you want to use in your form and the queryRun was constructed using this args object. Finally the query of the datasource (remember that 'this', in the code snippet, is the current datasource) is set to the query of the QueryRun.

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Dynamics Ax User Ranges

I recently received the following development question regarding Dyanmics Ax:

How to access the user specified ranges in a form i.e. the ranges set manually by the use, using right click 'Filter by field / Filter by selection' or using the advanced query button.

This is actually quite simple as shown in the following code:


public void executeQuery()
{
  Query query;
  int i;
  ;
  super();

  // get a pointer to the current query
  query = this.queryRun().query();

  // Traverse the ranges for the specified table in the query
  for (i = 1; i <= query.dataSourceTable(tableNum(custTable)).rangeCount(); i++)
  {
    // For each range field found display the field name and value chosen by user
    info(strfmt("Field Name: %1 ", fieldid2name(tableNum (CustTable), query.dataSourceTable(tableNum(custTable)).range(i).field())));
    info(strfmt("Value chosen by user: %1", query.dataSourceTable(tableNum(custTable)).range(i).value()));

// Or perform any other work
  }
}


The above code was written in the executeQuery() method of the DataSource to show the field names and values chosen by the user every time he makes a selection.

Tested on Ax 4.0 and 2009

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Tuesday, July 29, 2008

Traversing run time controls

Welcome to another AX development lesson.

Today i will discuss traversing controls created at run time to get/set their properties or use them in your code.

First, you have to get a reference to the formDesign of your form. This can be done by calling element.design() in your form. don't forget that you can pass this to as a paramter to a method, so you will be able to process the controls in a seperate class. You method has to accapet a formDesign variable as a parameter as shown in the following code:


public void controlsProcessor(FormDesign _formDesign)
{
  FormBuildControl formBuildControl;
  Counter counter;
  FormCheckboxControl formCheckboxControl
  ;

  for (counter = 1; counter <= formDesign.controlCount(); counter++)
  {
    // If current value of counter represent a valid control id in the form
    formBuildControl = formDesign.controlNum(counter);

    // If the current looped control is a textbox
    if (SysFormRun::controlType(classidget(formBuildControl)) == FormcontrolType::CheckBox)
    {
      // Assign it to a formCheckboxControl
      // to get access to properites and methods
      // usually found on checkboxes
      formCheckboxControl = formDesign.controlNum(counter);

      // If the checkbox is checked
      if (formCheckboxControl.value())
      {
      // Do your custom processing
      }
    }
  }
}


Obviously you can use any type of form controls not just checkboxes by using the corresponding control classes.

You could call the above method from a form by using the following line of code:


  myClassInstance.controlsProcessor(element.design);


If you any questions or comments e-mail me on: mirko@mirkobonello.com

Saturday, July 26, 2008

Creating a Form at Run Time

Welcome to another AX development lesson.

In this lesson I shall discuss how we can create a form at run time and add it to the AOT.

Here, we have to use various AX classes which are found under the System Documentation node in the AOT.

The class FormBuild is the base canvas on which we start building our form and on which our form level method have to be added. Therefore, we assign a new Form object (which resembles our form node in the AOT) and assign it to the FormBuild's form.

However, just like in standard AOT form design, we cannot add controls directly to the FormBuild but we need a FormBuildDesign class on which we will continue adding the rest of the controls.

We also need to add DataSources (if required) to the form object. Remember the form object is representing one form's route node in the AOT and your code has to rsemble the tree structure in the AOT i.e.

Form -> Methods
-> DataSources
-> Design -> Controls

The following job should make things clearer:


static void createFormAtRunTime(Args _args)
{
  TreeNode formlist;
  Form formNode;
  FormBuild formBuild = new FormBuild('MyRunTimeForm');
  FormBuildDesign formDesign;
  FormBuildTabControl formTab;
  FormBuildTabPageControl formTabPage;
  FormBuildDataSource formDatasource;
  FormBuildGridControl formGrid;
  XPPCompiler compiler;
  str dataSourceName;
  int dataSourceId;
  str sourceCode, methodName;
  ;
  methodName = 'runTimeMethod';
  sourceCode = @'
        void runTimeMethod()
        {
          ;
          info("hello");
        }';
  compiler = new XppCompiler();

  if (sourceCode && compiler.compile(sourceCode))
  {
    // Assign to formBuild's base 'canvas' to a form object
    // This resembles the top node of a form in th AOT
    // To this form object (form node) we add designs and datasources
    formNode = formBuild.form();
    formNode.AOTfirstChild();

    // Add ClassDeclaration
    formBuild.addMethod('classDeclaration', 'class FormRun extends ObjectRun     \n{}\n');

    // Add our previously built method
    formBuild.addMethod(methodName, sourceCode);

    // Add a new form Design
    formDesign = formNode.design();
    formDesign.columns(1);
    formDesign.leftMode(0);
    formDesign.topMode(0);

    // Add a new tab - header
    formTab = formDesign.addControl(FormControlType::Tab, 'Header');
    formTab.columns(1);
    formTab.autoDeclaration(true);
    formTab.widthMode(1); //column Width

    // Add a new tab page - overview
    formTabPage = formTab.addControl(FormControlType::TabPage, 'Overview');
    formTabPage.caption('Overview');
    formTabPage.widthMode(1); //column Width
    formTabPage.heightMode(1); //column Height

    formlist = infolog.findNode('Forms');
    formlist.AOTadd('MyRunTimeForm');

    // Add a DataSource - SalesTable
    formDatasource = formNode.addDataSource(tablestr(SalesTable));
    dataSourceName = formDatasource.name();
    dataSourceId = formDatasource.id();

    // Add a Grid
    formGrid = formTabPage.addControl(FormControlType::Grid,tablestr     (SalesTable)+'Grid');
    formGrid.heightMode(FormHeight::ColumnHeight);
    formGrid.widthMode(FormWidth::ColumnWidth);

    // Add fields to the Grid
    formGrid.addDataField(dataSourceId, fieldNum(SalesTable, SalesId));
    formGrid.addDataField(dataSourceId, fieldNum(SalesTable, CustAccount));

    // Commit changes - same like pressing Save button in the AOT
    formNode.save();
    formNode = formList.AOTfindChild('MyRunTimeForm');
    formNode.AOTrestore();
  }
}


If we did not have AX's Morphx/AOT features we would have to write all that code just to create a simple form like the one generated if you run the above code and open the form it generates and adds to the AOT.

That's all for this lesson!

See you next time!

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Compiling and running code at run time

Welcome to another lesson.

Here I will discuss how you can compile and run your code at run time. Similar to the scenario in the previous lesson, you might want to create certain code on the fly, when at compile time you do not know what should this code consist of. Therefore, you build the source code as a string based on certain conditions in your code.

Beware though, that this might pose a security risk and you must always call assert() on a valid (not null) SecurityPermission object before execute code which compiles and runs other code at run time. At the end of your dangerous code, you should then revert the permissions by using the following line of code:


  CodeAccessPermission::revertAssert();


Note: For the sake of clarity I will skip code relating to permissions in this lesson.

Let's get down to a simple example:


static void compileAtRunTime(Args _args)
{
  XppCompiler compiler;
  str sourceCode;
  ;
  sourceCode = @'
          static void runTimeMethod()
          {
            ;
            info("hello");
          }';

  compiler = new XppCompiler();

  // If some source code exists and it successfully compiled
  if (sourceCode && compiler.compile(sourceCode))
  {
    // Execute the source code
    // Displays an infolog with "hello"
    compiler.execute(sourceCode);
  }
}


Obviously, the above code is not very useful, because the source code is hardcoded. In practice you would build your source code based on different conditions that you want to cater for.

If you any questions or comments e-mail me on: mirko@mirkobonello.com

Friday, July 25, 2008

Invoke a method at run time

Welcome to this lesson.

Through the Dict classes, AX allows you to invoke a particular method at run time.

This is useful if you want to call a different method based on some other condition which you do not know at design/compile time.


The following is a simple example:


static void invokeFindOnCustTable(Args _args)
{
  // Create an instance of dictTable passing a table as parameter
  DictTable dictTable = new DictTable(tableNum('CustTable'));
  CustTable customer;
  ;
  // Check if CustTable has method 'find'
  if (tableHasStaticMethod(dictTable, identifierstr('find')))
    // Invoke find method on CustTable at runtime
    customer = dictTable.callStatic(
      tableStaticMethodStr(CustTable, Find), '4000');

  info (customer.Name); // Displays Light and Design in AX 4 Demo Data
}


Here is another example which executes an object method on a class, if it exists:

static void invokeFindOnAClass(Args _args)
{
  // Table buffers
  SalesTable salesTable;

  // Objects
  SysDictClass dictClass;
  SalestableType salesTableType;
  ;
  dictClass = new SysDictClass(classNum('SalesTableType'));

  salesTable = SalesTable::find('00021_036');

  salesTableType = SalestableType::construct(salesTable);

  // If 'SalesTableType' has method delete
  if (dictClass.HasObjectMethod(identifierstr('delete')))
    // Invoke object method 'delete'
    // passing an object instance on which to execute the method
    dictClass.callObject('delete', salesTableType);
}


Code of this post currently tested on:

Dynamics AX 4.0
Dynamics AX 2009

If you any questions or comments e-mail me on: mirko@mirkobonello.com