Pages

Thursday, September 19, 2013

Calling methods on a caller Dynamics AX form

To call methods on a caller form from another form or a class for example, you have to fetch the caller from the args object, keep it as an Object and just then you can call methods in it.

You have to keep it as an Object like the caller method returns it to you, because then the compiler allows you to call methods in a dynamic way. This means that there will be no compile-time checking for the method that you are trying to call, as it happens with any object of type Object in AX, so if you try to call a method that doesn't exist on the form, you'll get a run-time error.


Here's some example code:

Object caller = _args.caller(); 

    // Or if you're in a form:
    // caller = element.args().caller();

    caller.someMethodOnTheForm();


And just to add some defensive code so that you can avoid getting a run-time error, you could add the following validations to the same piece of code:

Object caller = _args.caller();

    if (caller is FormRun && formHasMethod(caller, identifierStr(someMethodOnTheForm)))
    {
        caller.someMethodOnTheForm();
    }

Now you're also checking if the caller is in fact a form, and also if it has the method you're trying to call.

Monday, September 16, 2013

Changing Dynamics AX's forms' caption / text

Dynamics AX's forms' caption is composed of two parts: The name of the form / table being edited, and the primary key or at least two key fields that tell the user what record he has selected.

But do you know how to change these texts? I'll show you some ways of doing it in this post.

The first part of the caption, the name of the form, can be set on the Caption property, on the Design node of the form:



And that change can be seen on the left side of the form's caption:



Additionally, you can change the left part of the form through coding. Just add the following line on the init method or on whatever other key method you want it to be:

element.design().caption('Foo');

The second part, which is some information about the record, can be changed in two ways: through property settings, or through X++. I'll show both of them.

The table has two properties that you can use for this, the TitleField1 and TitleField2, under the Appearance node:



After setting them, you'll have to set another property on the form's Design node, the TitleDatasource, which should be the datasource related to your table:



And that's it, the value on the two fields you set on the TitleField1 and TitleField2 properties will be included on the form's caption:



And the last way to do it, through X++, is by overriding the caption method on the table. Of course that since we're changing something directly on the table, our change will reflect throughout the whole system. This means that if you change the caption of a table by overriding the caption method, you'll change the caption of all of the forms where that table was used as the form's caption.

We can do something simple, like this:

public str caption()
{
    str ret;

    ret = super();
    
    if (this.RecId)
    {
        ret = strFmt('Item number: %1, Total sold amount: %2', this.ItemId, this.getTotalSoldAmount());
    }

    return ret;
}

private real getTotalSoldAmount()
{
    return this.UnitPrice * this.SoldQuantity;
}


With the methods above defined on our example table, our form should look like this:



We only change the behavior if the record already exists on the database, by checking if it has a RecId set. We do that because we don't want to change the default text that appears on the form when the user is inserting a new record:

Friday, August 16, 2013

The str2num function and decimal numbers

There seems to be an issue with the str2num function when what you want is a real value. This matters because it can be used to convert from a string to an int or a real.

In order to successfully convert from a string representation of a decimal value to an actual real type on X++, you can't have any formats on your string. That means that the string has to be representing a double exactly as the real variable does: no thousand separator, and the dot ('.') character for the decimal separator.
It does not consider AX's region when performing the conversion.


Consider the following job:

static void Job1(Args _args)
{
    str val1, val2, val3, val4;
    real real1, real2, real3, real4;
    
    val1 = '100,000.000';
    val2 = '100.000,000';
    val3 = '100000,005';
    val4 = '100000.056405';        
    
    real1 = str2num(val1);
    real2 = str2num(val2);
    real3 = str2num(val3);
    real4 = str2num(val4);
        
    pause;
}

The only valid conversion is the last one, from the variable val4. The real variables have the following values after the assignment:



So just remember to be very careful when converting from a string representing a decimal value to a real X++ type when using the str2num function.

You can always use the .NET Framework instead. If you do, then you'll be able to set a specific culture when converting the double value, like the following valid X++ code:

System.Globalization.CultureInfo cultureInfo = System.Globalization.CultureInfo::CreateSpecificCulture("pt-Br");
    real parsedDouble = System.Double::Parse("1.200,99", cultureInfo);
    
    print parsedDouble;

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.

Wednesday, July 31, 2013

Boolean parameter VS exposing semantics

This can be a subject of quite long discussions with team mates during coffee hours.
Some team mates might think that simply putting a boolean parameter that slightly changes the behavior of a method is not a big deal. They may even make the parameter optional.

But when is that a good thing? When is it good to "just add a boolean parameter and make it optional"? Well, if it changes the method behavior in the smallest way possible, but even then it matters, then I'd say it's never good.

To prove it, consider the following two calls:

object.DoSomeLengthyWork();
object.DoSomeLengthyWork(true);

I don't know about you, but if I'm the one maintaining this code the first thing that would pop into my head when reading the second line would be "What the hell should be true?". The second thing I'd have to do would be lookup the method definition (or read its summary, if the developer who wrote the method cared enough to even create one), which is pretty easy, just position the cursor over it and press F12 right? Right! But what if that method is defined in another project / solution / dll that you do not have opened in your environment, or you don't even have access to?

And that, my friend, is why I strongly advise you to always go for the better semantics! Specially if you're writing public methods.

What I'm saying here doesn't make much sense if you work in a small company with a small codebase and maybe a team of 2 to 3 developers. But imagine working in a team of 20, 30 or 50 developers. Or even with a small codebase, imagine maintaining code that has been in production for 5 years or more, with no documentation whatsoever. Code whose owner or writer is not even working for the company anymore. I'm sure you'd have a blast trying to find out "what the hell should be true" in the example above.

Wouldn't it be easier if you could just have those two example lines written like this instead?

object.DoSomeSynchronousLengthyWork();
object.DoSomeAsynchronousLengthyWork();

"Ooooh, so that's what that true meant!"

And I won't even mention named arguments for cases like this, because they're pretty evil for distributed solutions. Jon Skeet has an awesome way of stating it, which can be found here and of course, in his book C# in Depth, that I quote:


The silent horror of changing names

In the past, parameter names haven’t mattered much if you’ve only been using C#. Other languages may have cared, but in C# the only times that parameter names were important were when you were looking at IntelliSense and when you were looking at the method code itself. Now, the parameter names of a method are effectively part of the API even if you’re only using C#. If you change them at a later date, code can break—anything that was using a named argument to refer to one of your parameters will fail to compile if you decide to change it. This may not be much of an issue if your code is only consumed by itself anyway, but if you’re writing a public API, be aware that changing a parameter name is a big deal. It always has been really, but if everything calling the code was written in C#, we’ve been able to ignore that until now.

Renaming parameters is bad; switching the names around is worse. That way the calling code may still compile, but with a different meaning. A particularly evil form of this is to override a method and switch the parameter names in the overridden version. The compiler will always look at the deepest override it knows about, based on the static type of the expression used as the target of the method call. You don’t want to get into a situation where calling the same method implementation with the same argument list results in different behavior based on the static type of a variable.


And that my friends, is why when it comes to boolean parameters VS method overloading, I always side with method overloading, that is, when the boolean parameter changes the method's behavior in a way that matters.

PS: C# in Depth is a great read, you should definitely read it.

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);
    }