Wednesday, October 19, 2011

Send multiple field to another form or object (parmObject (Container))

1. Send
void clicked()
{
    Args  args = new Args();
    ContainerClass conClass_Obj;
    Container conSend;
   ;

         conSend = conins(conSend, 1, "Call Of Duty");
         conSend = conins(conSend, 2,"BattleField");
         conSend = conins(conSend, 3, "Assisins Creed");
         conSend = conins(conSend, 4, "retrive hell");
         conClass_Obj = new ContainerClass(conSend);
         Args.parmObject(conClass_Obj);
         new MenuFunction(menuitemdisplaystr(test_ParamObject_Recieve), MenuItemType::Display).run(args);
}
2. Receive
public void init()
{
    //super();
    str s1,s2,s3,s4;
    containerClass conClass_Obj;
    container conRec;
    Args args;
;
    super();
    args = new args();
    conClass_Obj =  element.args().parmObject();
    conRec = conClass_Obj.value();
    s1 = conpeek(conRec,1);
    s2 = conpeek(conRec,2);
    s3 = conpeek(conRec,3);
    s4 = conpeek(conRec,4);
    info(strfmt("Value1 = %1 , Value2 = %2 , Value3 = %3 ,Value4 = %4",s1,s2,s3,s4));
}

Monday, October 17, 2011

Instantiating Query Objects

Constructor of the Query class has default anytype parameter named _source. To my knowledge there are four different ways to use it:
  1. Instantiating an empty query – no parameter value should be specified:
    Query q = new Query();
  2. Instantiating a query based on the AOT query:
    Query q = new Query(querystr(Alertsetup));
  3. Instantiating a query as the copy of another query:
    Query q1 = new Query();
    Query q2 = new Query(q1);
  4. Instantiating a query from the container with the packed query
    Query q1 = new Query();
    Query q2 = new Query(q1.pack())

Tutorial: refresh, reread, research, executeQuery - which one to use?

X++ developers seem to be having a lot of trouble with these 4 datasource methods, no matter how senior they are in AX.
So I decided to make a small hands-on tutorial, demonstrating the common usage scenario for each of the methods. I have ordered the methods based on the impact on the rows being displayed in the grid.
You can download the xpo with the tutorial on my SkyDrive.

1. Common mistakes

Often, developers call 2 of the mentioned methods in the following order:
formDataSource.refresh()
formDataSource.research()

or
formDataSource.reread()
formDataSource.research()

or
formDataSource.research()
formDataSource.executeQuery()

or
formDataSource.research()
formDataSource.refresh() / formDataSource.reread()

All of these are wrong, or at least partially redundant.
Hopefully, after reading the full post, there will be no questions as to why they are wrong. Leave a comment to this post if one of them is still unclear, and I will try to explain in more detail.

2. Refresh

This method basically refreshes the data displayed in the form controls with whatever is stored in the form cache for that particular datasource record. Calling refresh() method will NOT reread the record from the database. So if changes happened to the record in another process, these will not be shown after executing refresh().
refreshEx
Does a redraw of the grid rows, depending on the optional argment for specifying the number of the record to refresh (and this means the actual row number in the grid, which is less useful for AX devs). Special argument values include -1, which means that all records will be redrawn, and -2, which redraws all marked records and records with displayOptions. Default argument value is -2.
This method should be used sparingly, in cases where multiple rows from the grid are updated, resulting in changes in their displayOptions, as an example. So you should avoid using it as a replacement for refresh(), since they actually have completely different implementations in the kernel.
Also, note, that refreshEx() only redraws the grid, so the controls not in the grid might still contain outdated values. Refresh() updates everything, since this is its intention.

3. Reread

Calling reread() will query the database and re-read the current record contents into the datasource form cache. This will not display the changes on the form until a redraw of the grid contents happens (for example, when you navigate away from the row or re-open the form).
You should not use it to refresh the form data if you have through code added or removed records. For this, you would use a different method described below.
How are these 2 methods commonly used?
Usually, when you change some values in the current record through some code (for example, when the user clicks on a button), and update the database by calling update method on the table buffer, you would want to show the user the changes that happened.
In this case, you would call reread() method to update the datasource form cache with the values from the database (this will not update the screen), and then call refresh() to actually redraw the grid and show the changes to the user.
Clicking buttons with SaveRecord == Yes
Each button has a property SaveRecord, which is by default set to Yes. Whenever you click a button, the changes you have done in the current record are saved to the database. So calling reread will not restore the original record values, as some expect. If that is the user expectation, you as a developer should set the property to No.

4. Research

Calling research() will rerun the existing form query against the database, therefore updating the list with new/removed records as well as updating all existing rows. This will honor any existing filters and sorting on the form, that were set by the user.
Research(true)
The research method starting with AX 2009 accepts an optional boolean argument _retainPosition. If you call research(true), the cursor position in the grid will be preserved after the data has been refreshed. This is an extremely useful addition, which solves most of the problems with cursor positioning (findRecord method is the alternative, but this method is very slow).

5. ExecuteQuery

Calling executeQuery() will also rerun the query and update/add/delete the rows in the grid. The difference in behavior from research is described below.
ExecuteQuery should be used if you have modified the query in your code and need to refresh the form to display the data based on the updated query.
formDataSource.queryRun().query() vs formDataSource.query()
An important thing to mention here is that the form has 2 instances of the query object - one is the original datasource query (stored in formDataSource.query()), and the other is the currently used query with any user filters applied (stored in formDataSource.queryRun().query()).
When the research method is called, a new instance of the queryRun is created, using the formDataSource.queryRun().query() as the basis. Therefore, if the user has set up some filters on the displayed data, those will be preserved.
This is useful, for example, when multiple users work with a certain form, each user has his own filters set up for displaying only relevant data, and rows get inserted into the underlying table externally (for example, through AIF).
Calling executeQuery, on the other hand, will use the original query as the basis, therefore removing any user filters.
This is a distinction that everyone should understand when using research/executeQuery methods in order to prevent possible collisions with the user filters when updating the query.

Batch job performance boost

Did you ever have trouble with the performance of your batch-job? Well maybe this small trick can shorten the time your batch runs.
The big idea: Try to split up your gigantic batch in smaller pieces. Launch a batchjob that produces many smaller batchjobs (tasks) that can handle a subset of the data you need to process. For example you can create a batch that creates a batch with sub-tasks for each company.
How do you plan a batch from code?
1
2
3
4
5
6
7
8
9
10
11
12
BatchHeader batHeader;
BatchInfo batInfo;
TstRunBase rbbTask;
;
rbbTask = TstMyBatch::construct();
batInfo = rbbTask.batchInfo();
batInfo.parmCaption("MyBatch");
batInfo.parmGroupId("");
batHeader = BatchHeader::construct();
batHeader.addTask(rbbTask);
batHeader.save();
info(batInfo.parmCaption());
You can download a quick example I made. This is a job you can schedule and it will produce a new Batch job with a task for each company when you don’t select a company while shedualing (Class_TstRunBase.xpo). I hope this can help you.
Schedule the batch:

The result:

Setting propperties on a FormControl without AutoDeclaration

When you want to set a property of a control on a form without setting the property AutoDeclaration = yes. You can address the control trough the following code, knowing that element is a FormRun-object:
 
------------------------------------------------------------
element.design().controlName("btnOk").visible(false);
------------------------------------------------------------

Create class from code

Did you know that you can build/modify classes from code? It is actually not so hard, just use the ClassBuild-class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void Kishor_testClassBuild(Args _args)
{
    ClassBuild  classBuild;
    ClassNode   classNode;
    Str         myCode;
    ;
    myCode =
@"
static void Main(Args _args)
{
    ;
    info('Hello World!');
}
";
 
    classBuild = new ClassBuild("MyTest", false);
    classBuild.addMethod("Main", myCode);
 
    classNode = classBuild.classNode();
    classNode.AOTcompile();
    classNode.AOTrun();
}
The sample above will create a new class MyTest with a ‘Hello World’ Main-method and will actually save compile/save the code in the AOT and then run it. Cool?
Note: The SysDictClass/SysDictMethod-class can also help you creating proper classes and methods. Maybe I’ll blog about this later.