Pages

Showing posts with label Dynamics AX. Show all posts
Showing posts with label Dynamics AX. Show all posts

Thursday, February 27, 2014

Disable auto complete on a form control

As a pattern, Dynamics AX has an auto complete feature in form controls.

There is a way to disable it though, programmatically.


There's the delAutoCompleteString method on the FormRun class. The name is pretty straight-forward.


We must override the control's textChange method to call it:

public void textChange()
{
    super();

    // Since we're overriding a control's method, "this" means the instance of the control.
    // We could also reference it by its name, if the AutoDeclaration property is Yes.
    element.delAutoCompleteString(this);
}


With that single line of code, we can disable the auto complete feature.

Monday, August 12, 2013

Displaying a SysInfoAction button on the Infolog

Actioning it

Have you ever found yourself in a situation where the Infolog keeps throwing up the same error / warning at you and you have no idea how to resolve it?
For example: "The field Foo must be set to Y to perform this action". That's a pretty clear message: you just have to set the Foo field to Y and you're good to proceed with what you were doing, except that you have no idea where the Foo field is! When I started working with AX, I found myself in this situation more than I'd like to, and it always annoyed me to have to call someone and ask where the damn Foo field was.

But sometimes, the Infolog would show me a magic button that would take me exactly to wherever I had to go to fix the error it was showing me. That magic button is called a SysInfoAction. For the example I have given above, if the good guy programmer had put a SysInfoAction on the Infolog, you could just click on a button and a form with the Foo field would pop up.


So how do you do it? Well, there are some different SysInfoAction types. Just to list a few of them:

  • SysInfoAction_CostSheetValidate
  • SysInfoAction_Editor
  • SysInfoAction_Formrun
  • SysInfoAction_MenuFunction
  • SysInfoAction_newWindow
  • SysInfoAction_Properties
  • SysInfoAction_Showplan
  • SysInfoAction_TableField


As you'd expect, each one of these classes exposes a different behavior when the user interacts with the button on the Infolog.

Let's take a look at the SysInfoAction_MenuFunction for example.

It does what the name suggests: adds a menu item on the Infolog. And it can be of any type: Action, Display or Output.

Suppose we want to redirect the user to a form so that he can fill in the "Foo" field:

SysInfoAction_MenuFunction sysInfoAction;

sysInfoAction = SysInfoAction_MenuFunction::newMenuItem(menuitemDisplayStr(FooFormMenuItem), MenuItemType::Display);

warning('The field Foo must be set to Y to perform this action', '', sysInfoAction);

Just by creating that object and passing it to the warning() method, you made the user's life a lot easier. There's also a constructor available to the SysInfoAction_MenuFunction where you can pass in the control's name of the form you're opening, and the framework will set the focus to that control if it's found on the form. Although it helps even more, the call to the method should be ugly, and easy to break, since there is no compile-time checking if the control actually exists on the form.
The good part though is that if the control isn't found, it just doesn't do anything.

But what about security?

I have to say that the security framework team did a tremendous job overall, and it's just the same with the SysInfoAction feature.
The SysInfoAction, in this case, is nothing more than a menu item. Menu items are bound with security objects in various ways, so it's kind of easy to get the related roles or duties. That being said, guess what happens if the user doesn't have the permission to run the MenuItem you have added to the SysInfoAction? The user doesn't see the button.
So if you're worrying that you might accidentally redirect a user to an important parameters form which he shouldn't have access to, don't worry, because you wont: the security framework will take care of that for you.

That all being said, I'd advise to always use SysInfoActions where it really improves the user experience.

Friday, July 5, 2013

Change CreatedDateTime field content

Since it's a table column controlled by the system, you can't update it. The same goes to the other system columns.

The compiler won't even let you write code like the following:

myTable.CreatedDateTime = DateTimeUtil::utcNow();

It'll spit out the error "The field must be a data element that allows assignment.".


However, there is a workaround to be done. The xRecord.overwriteSystemFields helps us with it. Its name is pretty straightforward. We should also grant the required permission to our code, by using the OverwriteSystemFieldsPermission permission class.

Here's how we should use it:

MyTableBuffer myTableBuffer;
    
    myTableBuffer = myTableBuffer::find();
    
    new OverwriteSystemfieldsPermission().assert();
    
    myTableBuffer.overwriteSystemfields(true);
    myTableBuffer.(fieldNum(MyTableBuffer, CreatedDateTime)) = DateTimeUtil::utcNow();
    
    myTableBuffer.insert()
    
    CodeAccessPermission::revertAssert();


There are two things to keep in mind:

  • This trick only works for insert operations. It will not work for update operations.
  • The OverwriteSystemFieldsPermission will only work for code running on the server.


To make your code run on the server, add the "server" keyworkd to its name, like this:

server public static void foo()

Just remember that the server keyword only works for static methods. In MSDN's words:

Establishes the location where the method is to be executed (on the server).
Can only be used on static methods. If the method is not static, you need to specify the location using the class property RunOn.

Thursday, July 4, 2013

Dynamics AX custom lookup in dialog

If you ever need to create a Dynamics AX dialog custom lookup, don't use the method naming approach, in which you name your method with a sufix like "_lookup". Your code will get very ugly, because the name of the fields in a dialog have very weak semantics. You'll most likely end up with methods like:

public void Fld1_1_lookup()

Instead use the new method introduced in Dynamics AX 2012, the DialogField.registerOverrideMethod method.


The method is very straightforward: you indicate what method you want to override, what is the method that overrides it, and in which form you want it to be overridden. Since it works with dialogs, you can use it on SysOperation UI Builders, on forms' dialogs and so on.

Because this method requires that the dialog is already constructed, you usually put it in "postRun" methods, like dialogPostRun or simply postRun, it depends on what you're doing. For SysOperationUIBuilders, you should call this method on the postBuild method.


For what I've seen and tested, the method that will receive the "event" we have overridden must always accept a first parameter of type FormControl, otherwise it just won't work.

Your method should look something like this:

private void lookup(FormControl _formControl)


Here's a full example of a lookup for a SysOperationUIBuilder, where we will override a lookup for a dialog field bound to the data contract:

  • Here we register the method that will be used to override the lookup method
    public void postRun()
    {
        DialogField           dlgFieldToBeOverridden;
        MyDataContract        dataContract;
        
        super();
    
        dataContract = this.getDataContractObject();
    
        dlgFieldToBeOverridden = this.bindInfo().getDialogField(dataContract, methodStr(MyDataContract, parmFieldToBeOverridden));
    
        dlgFieldToBeOverridden.registerOverrideMethod(methodStr(FormStringControl, lookup), methodStr(MyUIBuilderClass, myCustomLookup), this);
    }
    
  • And here's the method that will perform the actual lookup. For this particular example, we will assume we already have developed a new form called "MyLookupForm", and that we will be performing the lookup from a string control. You could use the SysTableLookup or the SysReferenceTableLookup approaches instead.
    private void myCustomLookup(FormControl _formControl)
    {
        Args                args            = new Args(formStr(MyLookupForm));
        FormStringControl   stringControl   = _formControl;
        FormRun             formRun;
    
        formRun = ClassFactory::formRunClassOnClient(args);
        formRun.init();
    
        stringControl.performFormLookup(formRun);
    }
    

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 :(.

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.

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, February 7, 2012

Dynamics AX Custom Lookup

As a Dynamics AX developer, you'll often have to perform custom lookups. Meaning that the user may only select records in another table depending on some condition on the form, or elsewhere.


There are two ways of creating custom lookups. I'll show the easiest here, and maybe show the other way in the future.

So if you're thinking you'll have to create a new form and all that, forget it, you won't. All you have to do is write some pieces of code.



First let's create our lookup method directly on the table. You can check other tables for these special lookup methods. They appear in lots of tables. Take for example the table ContactPerson. It has the following custom lookup methods by default:

  • lookupActionContactPerson
  • lookupCustContactPerson
  • lookupDirContactPerson
  • lookupDirContactPersionId
  • lookupVendContactPerson



Now, to create a custom lookup, we'll use the class SysTableLookup as it provides us a set of methods to do so.


Here's our method, to be created directly on the table DummyTable, with some dummy data and names.

public client static void lookupDummyTable(FormStringControl _ctrl,
                                           MyFilterEDT _filter)
{
  
    // Intantiantes a SysTableLookup object telling it which control activated the lookup, and
    // what table should be displayed inside the lookup form.
    SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(DummyTable), _ctrl);

    Query query = new Query(); // This query will be used by the lookup form.
    QueryBuildDataSource qbds;

    ;

    // The "addLookupField" method adds the the fields that will be shown inside the lookup's grid. 
    // The second, boolean parameter indicates that this field will be returned from the lookup when a
    // record is selected. In this case, we'll return our DummyPK.
    sysTableLookup.addLookupField(fieldnum(DummyTable, DummyPK), true); 
    sysTableLookup.addLookupField(fieldnum(DummyTable, Field1));
    sysTableLookup.addLookupField(fieldnum(DummyTable, Field2));
    sysTableLookup.addLookupField(fieldnum(DummyTable, Field3));


    // Using our dummy table as a DataSource Table.
    qbds = query.addDataSource(tablenum(DummyTable));

    // This is the key part, what we want to filter to be displayed in the grid.
    qbds.addRange(fieldnum(DummyTable, MyFilterColumn)).value(_filter);


    // Passes the query to the sysTableLookup.
    sysTableLookup.parmQuery(query);

    // Activates the lookup.
    sysTableLookup.performFormLookup();
}



With that little piece of code, Dynamics AX will already display a lookup form with the table you've specified as a DataSource, displaying the fields you specified with the method addLookupField and following the results criteria of the Query you've passed to it.



Now all we have to do is actually call our lookup method, by overriding the lookup of the correct field in our Form's Data Source:


public void lookup(FormControl _formControl, str _filterStr)
{
    ;

    DummyTable::lookupDummyTable(_formControl, "SomeFilter");
}


And that's it! The lookup will be performed. But there's one additional step we'll have to take here...

When the user tries to select our DummyTable reference, Dynamics AX will display the results of the query, based on the filter we've created. So the user will only be able to see what we want him to see, and so he'll only be able to select what we want. The problem is that if he types in an existing record for the DummyTable in the control, even if does not get shown in our lookup, Dynamics AX will accept that. Remember: we're only limiting what the user may see and select for that field, but we're not limiting what he can actually type in the control. So if we don't validate it somehow, Dynamics AX will run the standard validation, which will only check if a record exists for that EDT and so on. To avoid that the user does in fact type in something we don't want, we'll have to override the validate method for our field in our Data Source. The simplest logic is to check if the DummyTable record the user typed in does attend the filter we've specified:


public boolean validate()
{
    DummyTable    dummyTable;

    ;

    dummyTable = DummyTable::find(currentRecord.DummyTableRefField);

    return dummyTable.MyFilterColumn == "SomeFilter";
    
}



So if the user simply types in for a record that does not attend our filter, Dynamics AX won't let the user save that value.



This is the easiest way to effectively implement a custom lookup and validate the user input in case he doesn't select the record from the lookup. For this example I used a literal filter, a string. But in most cases, you'll have to filter according to what the user selected for another field in your form. Things should usually be dynamic.

Hope this helps...

Thursday, February 2, 2012

Refresh DataSource and retain position

Update: Also read "How to effectively refresh data on forms".

Refresh a DataSource is a very common task to Dynamics AX developers, as you'll most likely perform changes on a record and have to present it to the user, on a form.


The most commonly used method to refresh the DataSource to make a form show what you have changed on a record is the research method.
The only problem is that the research method doesn't retain the record that was selected, it positions the DataSource on the first record of the result, causing the user to see another record. To resolve this matter, the research method, only in Dynamics AX 2009, has a boolean parameter, retainPosition. Now if you call it by passing true to it, it will sure retain the position of the selected record after the refresh, right? Wrong...


This should only work when your form has DataSources linked by Inner Joins in it, what means that the generated query has Inner Joins in it. Having any other link types or temporary tables causes it to not retain the position, even if you pass true when calling it.

So I'll present you two ways of retaining the position of the selected record, one that works sometimes, and one that always works, although you should always try and see if it works with research(true) before trying anything else.


On the first way, you could simply get the position of the DataSource before calling research(), like this:

int position;

    ;

    position = myTable_ds.getPosition();
    myTable_ds.research();
    myTable_ds.setPosition(position);

But does this work perfectly? Not again...

The code above should select the record that was on the previously selected record's position. What does that mean? It means that if your query result changes the record's orders, you won't have the correct record selected. This could easily happen if your form handles the records' order in a special way, or if the action is performed when the record or any records are inserted, which will certainly cause the the form to reorder the records when you tell it to refresh the DataSource.


The second way to do it will actually look for the correct record and then select it, so you can imagine it will perform a little worse than the others, but at least it will never fail (unless you, somehow, delete the record you're looking for in the meantime).


Simply copy the RecId from the record you want to keep selected to a new table buffer, like this:


MyTable      myTableHolder;
    
    ;

    myTableHolder.RecId = myTableRecord.RecId;
    
    myTable_ds.research(); // or: myTableRecord.dataSource().research();

    myTable_ds.findRecord(myTableHolder); // or: myTableRecord.dataSource.findRecord(myTableHolder);     

We copy if to a new table buffer because the findRecord method expects a parameter of type Common. If you stored the RecId in a variable, you would have to pass it to a table buffer anyway...


And that's it, it'll first refresh the DataSource, and then look for your record and select it, so the screen might even "blink" for a second. As I said, this is not the best when thinking about performance, but at least it works...

Friday, December 23, 2011

Post or Validate Invent Journal

When you want to post an Invent Journal through code, you can use the class InventJournalCheckPost, like this:



static void Job1(Args _args)
{

    InventJournalCheckPost      inventJournalCheckPost;
    InventJournalTable          inventJournalTable;
    ;


    inventJournalTable = InventJournalTable::find("MyJournalID");

    
    inventJournalCheckPost = InventJournalCheckPost::newPostJournal(inventJournalTable);
    inventJournalCheckPost.parmThrowCheckFailed(true);

    try
    {

        inventJournalCheckPost.run();
    }
    catch
    {
        info("An error has occurred, journal not posted.");
    }
}


When setting parmThrowCheckFailed to true, when the journal fails to be posted by any reason, Dynamics AX will throw an error with the error message. If you don't, it will simply show the error message on the infolog, but, technically, you wont know wether it posted successfully or not, so it depends on what you're trying to do. This is specially useful when you're working with transactions and depend on the journal being posted successfully to continue with your code or to abort everything.



If you're simply trying to Validate (Check) an Invent Journal, all you have to do is change which constructor you'll use for the class InventJournalCheckPost, like this:


static void Job1(Args _args)
{

    InventJournalCheckPost      inventJournalCheckPost;
    InventJournalTable          inventJournalTable;
    ;


    inventJournalTable = InventJournalTable::find("MyJournalID");

    // Constructor to just check the journal:
    inventJournalCheckPost = InventJournalCheckPost::newJournalCheckPost(JournalCheckPostType::Check, inventJournalTable);
    inventJournalCheckPost.parmThrowCheckFailed(true);

    try
    {

        inventJournalCheckPost.run();
    }
    catch
    {
        info("The journal contains errors.");
    }
}


And all the other classes for Journals go with the same base, so for example, for a Production Journal, the correct class to perform checking or posting is ProdJournalCheckPost. That is because all of them inherit from JournalCheckPost or JournalCheckPostLedger (which inherits from JournalCheckPost).


So that's it, that's how you post or check a journal with code.