Pages

Saturday, June 8, 2013

Execute Menu Items through code

I've seen some people trying to find out how to execute or run Menu Items programatically on the internet.

So I thought I'd best blog about it.

We have three types of Menu Items: Action, Output and Display.

An action is frequently a class that performs some business operation, an output is a report and a display is a form.

Menu Items are represented by the MenuFunction class. Here's some code example to run Menu Items through code:

MenuFunction menuFunction;

menuFunction = new MenuFunction(menuItemDisplayStr(MyDisplayMenuItem), MenuItemType::Display);
menuFunction.run();

Of course the code above may be changed to execute different kinds of Menu Items, for example, the code below runs an Output Menu Item:

MenuFunction menuFunction;

menuFunction = new MenuFunction(menuItemOutputStr(MyOutputMenuItem), MenuItemType::Output);
menuFunction.run();

And if you need to pass any arguments to the Menu Item you're trying to execute, you can pass it with an Args object. The run method accepts an Args parameter, like this:

Args args = new Args();

args.record(myArgumentRecord);

args.caller(this);

new MenuFunction(menuItemOutputStr(MyOutputMenuItem), MenuItemType::Output).run(args);

You should always use functions to get the name of the menu item instead of using a hardcoded string. This guarantees that if someone changes the name of the menu item, your code stops compiling and you have time to fix it before shipping it. The following are the three functions used to get the menu items' name. Their names are pretty straightforward:


Here's a link to a Stack Overflow question that I answered but didn't get the answer :(.

Friday, June 7, 2013

Dynamics AX Custom Reference Group Lookup

Microsoft Dynamics AX 2012 introduced a new form control type: the reference group.

And also, very often we'll have to customize the contents of the lookup, or filter them. We can do this by using the SysReferenceTableLookup class.


So go to your form's data source and find the reference field for which you want to perform the lookup. Override its lookupReference method.

The use of the SysReferenceTableLookup is very similar to the SysTableLookup: we construct a Query, add data sources and ranges to it, and then pass it to the SysReferenceTableLookup object, like the code below:


public Common lookupReference(FormReferenceControl _formReferenceControl)
{
    SysReferenceTableLookup     sysRefTableLookup;
    Query                       lookupQuery = new Query();
    QueryBuildDataSource        lookupQueryDataSource;

    // Construct the SysRefTableLookup object
    sysRefTableLookup = SysReferenceTableLookup::newParameters(tableNum(MyTable), _formReferenceControl)

    // Add the field list that will be displayed on the lookup form
    sysRefTableLookup.addLookupfield(fieldNum(MyTable, MyFirstField));
    sysRefTableLookup.addLookupfield(fieldNum(MyTable, MySecondField));

    // Construct the query's data source
    lookupQueryDataSource = lookupQuery.addDataSource(tableNum(MyTable));

    // Add ranges to the query data source
    lookupQueryDataSource.addRange(fieldNum(MyTable, MyFirstField)).value(queryValue('Foo'));
    lookupQueryDataSource.addRange(fieldNum(MyTable, MySecondField)).value(queryNotValue(''));

    // Pass the query to the lookup object
    sysRefTableLookup.parmQuery(lookupQuery);

    return sysRefTableLookup.performFormLookup();
}


Unlike the SysTableLookup, the SysReferenceTableLookup returns an object of type Common which is the record the user selected on the lookup.

For this example I created the lookup logic directly on the form, but you could always create a lookup method directly on your table, and then make the control's lookup method call the table one. It's entirely up to you.

Just don't forget to validate the user input, because filtering the lookup does not stop the user from typing in a value on the control instead of selecting the record on the lookup. You could do it by overriding the control's validate method or directly on the table with the validateField method.

Monday, June 3, 2013

Get selected records on the grid / datasource

This should also be a common task for Dynamics AX developers.

Fortunately, there's a little helper class in AX to make it easier for us, the MultiSelectionHelper.


For example, if you want to get a set of selected records in a grid, you could use it like this:

MyTableBuffer            myTableBuffer;
MultiSelectionHelper     selectionHelper = MultiSelectionHelper::construct();
Set                      selectedRecords = new Set(Types::Record);


selectionHelper.parmDataSource(myTableBuffer_DS);

myTableBuffer = selectionHelper.getFirst();

while (myTableBuffer)
{
    selectedRecords.add(myTableBuffer);

    myTableBuffer = selectionHelper.getNext();
}


The code above should be very useful when getting the list of selected records directly on the form, but if you want to get the selected records in a class that was called from a form, for example, you could use the MultiSelectionHelper like this:

public static void main(Args _args)
{    
    FormDataSource          formDataSource;    
    MyTableBuffer           myTableBuffer;
    FormRun                 caller = _args.caller();
    MultiSelectionHelper    helper = MultiSelectionHelper::createFromCaller(caller);
    Counter                 i;

    // First we need to get the correct form data source
    for (i = 1; i <= caller.dataSourceCount(); i++)
    {
        formDataSource = caller.dataSource(i);

        if (formDataSource.table() == tableNum(MyTableBuffer))
        {
            break;
        }
    }

    // We then tell the selection helper object to create ranges for the selected records
    helper.createQueryRanges(formDataSource.queryBuildDataSource(), fieldStr(MyTableBuffer, RecId));

    // Now we can traverse the selected records
    myTableBuffer = helper.getFirst();

    while (myTableBuffer)
    {
        info(myTableBuffer.RecId);

        myTableBuffer= helper.getNext();
    }
}
In the example above we received the caller form with the _args object. Then, we have to find the correct FormDataSource object. This should be the table for which we want to get the list of records. After getting the correct data source, we can then tell the selection helper object to create ranges for the selected records on the specified QueryBuildDataSource, which we can now get directly from the FormDataSource in Dynamics AX 2012, with the queryBuildDataSource method. And the last part is the same as the first code example, only this time I didn't add the selected records to a set, I just showed their RecId on the Infolog. Since we need to get the FormDataSource for this latter approach, we can only use it if our form only has one data source for the table we're trying to get.

This should be very useful when you need to perform certain action on a list of selected records. Hope it helps.

Tuesday, April 23, 2013

Getting the last error message from the Infolog

Even though you may think that you'll often have to do this, you won't. If you come to a situation where you'd consider doing this, you better think again, because there's always a workaround.

Still, if you really want to do this, you can do the following:


str errorMessage;

try
{
    throw error('Some error message being thrown here');
}
catch
{
    errorMessage = infolog.text(infologLine());
}

print errorMessage;

pause;

The infolog.text() method gets the text of the line number argumented. The infologLine() global method gets the total of lines in the Infolog. The code above always gets the last line added to the Infolog.


The code above will not hide the Infolog too. If you want to capture the last error message and then hide the infolog, or clear its contents, you can add a call to the infolog.clear() method right after you fetch the error message.


One thing to point out is that the infolog.text() method returns you the contents of the line number specified as text. This means it'll cast the line icon to a string too, making it become a tab character. You'll have to parse the returned string, because it'll be something like " Some error message being thrown here".

You can simply add a call to the strRem method to remove the tab character:

errorMessage = strRem(errorMessage, '\t');

Thursday, April 18, 2013

Be careful with Country Region Codes

When developing in X++, be careful when assigning some Extended Data Type to a table column, for example.

Extended Data Types have the Country Region Codes property, which, as the help text says, is a "Comma separated list of Country Region Codes where this can be shown".

This means that if you are working on a localization project for a Brazilian company for example, and you use an Extended Data Type which Region Code is "FR", for France, on a table column, that column won't be shown. Simple as that, it will not appear anywhere in any form.

You should always use the correct Country Region Code for your EDTs, depending on the country of the Legal Entity you're working with.

Monday, March 4, 2013

How to effectively refresh data on forms

Whenever you change some data in another process and you want to display it back on the form, you have to somehow refresh the values on the form.
On this post, I'll try to explain which methods we have to do that, and how and when to use them.


FormDataSource.refresh

The first method, available on the FormDataSource class, is the one that seems to be the most intuitive: the refresh method.

What it does is put the data stored in the form cache, for the current record on the controls. This means that it won't go all the way back to the database to ask it again for the record. In other words, data modified for that record on another process, outside of the form, won't be displayed since the cache won't be refreshed. If you need this, you can use the method below.

FormDataSource.research

The research method will do what the refresh won't do: it will ping the database and query it again with the form generated query. Because it does this, it will update all of the records on the data source.

There's also the optional parameter bool _retainPosition. When called with the argument true, it preserves the cursor position on the form's grid. This is very useful when you don't want the user to "lose" track of what he was doing, or to highlight whatever changed on the record.


FormDataSource.reread

This method is pretty straightforward: it simply rereads the current record on the data source. Also, this doesn't refresh any value that the user sees on the form. It only updates the record on the form data source level.

One common use of this method is to call the refresh method too right after.


FormDataSource.executeQuery

The executeQuery method also queries the database again and updates all of the records on the form data source. You should use this method whenever you modify the form's query, by adding a range or removing one, for example.


These are the ways of refreshing data in a form. And here's the post that helped me writing this one.

Tuesday, January 29, 2013

The Garbage Collector does not call the Dispose() method!

If you have ever wondered if the Garbage Collector calls the Dispose method on objects that implement the IDisposable interface, the answer is no.

It calls the Finalize method, which does nothing by default. If you have any unmanaged or additional resources that you want freed or released after the lifetime of an object, you must call the properly overriden Dispose method. I won't get in details on the Dispose pattern or anything like that, not on this post. Maybe on the future...

Anyway, this caught my attention because we had lots of objects that used unmanaged resources and they were not being freed, causing some memory leaks. The Dispose method was correctly implemented on these objects but the only thing is that it wasn't being called at all!

So my suggestion is that you always use try-catch-finally blocks or using blocks when dealing with disposable objects, specially when they use unmanaged resources. Make sure that you always call the Dispose method.

Simple example of a Foo class that uses some unmanaged resources inside of an using block, assuming that the Dispose method effectively frees any resources that the object uses:

using(var disposableFoo = new Foo())
{
    // Use your disposable foo
}