Tuesday, 28 August 2018

Extension of classes, tables and forms in D365FO/AX 7.0


First method is of course the legacy method of creating a derived class which we want to extend, adding or overriding base methods in it and use this class in our new model instead of the base class. This solves many of the customization work we need but there might be cases this method is not desirable or we cannot modify the code of existing tables and forms like that.
Second option is to create something new called an ‘extension class’ to extend the program code of an existing object. This is simply done by creating an extension class with the template shown below using the ‘ExtensionOf’ attribute:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[ExtensionOf(classStr(MyClass))]
final class MyClass_Extension
{
private int myParm;
private void new()
{
}

public int parmMyParm(int _myParm = myParm)
{
myparm = _myParm;
return myParm;
}

public static MyClass constructFromMyParm(int _myParm)
{
MyClass myClass;

myClass = new MyClass();
myClass.parmMyParm(_myParm);
return myClass;
}
}
This way you can add new methods, variables, constructors and static methods directly added to the base class instead of using a derived class. After this, you can call the new methods and constructors you have added in your extension class directly from the base class itself, such as:
MyClass::constructFromMyParm(24).
The suffix of the extension class you have created must be ‘_Extension’ and you can name the MyClass part with anything you want, it does not have to be the base class name. Same way you can create code extensions for tables and forms (as of version update2) as well, later we will try an example with each.
Although this looks like a derived class construction, the extension class is not a derived class from the base and you cannot access private and protected methods and variables, nor override existing methods of the base class here. But it is possible to access public methods and variables of the base class as well as using ‘this’ keyword in your method declarations to access those. It is also possible to access the methods of other extensions on the same base object if you have added the model that includes the other extension class in your model definition’s referenced packages.
I also want to mention the extension class template below. Before the “Update1” version of AX7, the template for creating the extension class for tables and classes was like below :
1
2
3
4
5
6
7
8
public static class MyClass_Extension
{
public static int parmMyParm(MyClass _this, int _myParm = myParm)
{
myparm = _myParm;
return myParm;
}
}
Here you declare your class and all the class methods as static (class with _Extension suffix) and add the object you want to extend as the first parameter of your method. I do not recommend using this template in your new program code because the new one is more readable and useful, but be aware: On runtime your new notation extension class works exactly the same way using static method calls, just like the old notation did. This is why you cannot access class private and protected variables nor override existing methods in it. Also for the same reason you should not create a direct instance of the extension class in your program code, nor create another class derived from your extension class (it also receives a “sealed” attribute when built into CIL, like other static classes in .net). You can confirm it for yourself using visual studio debugger:
Capture11
Capture12
Creating table methods is pretty the same, you can even create new display and edit methods as shown below, to be used in a form (or form extension) in the same model :
1
2
3
4
5
6
7
8
9
10
11
12
13
[ExtensionOf(tableStr(InventTable))]
final class ExtensionDemo_Extension
{
private void new()
{}

[SysClientCacheDataMethodAttribute(true)]
public display Description dispExtendedDesc()
{
return this.Description5+this.Description6;
}

}
To be able to use this display method in your form, you need to define the “Data method” property of your data field like this, using static method operator and name of your extension class (“ExtensionDemo_Extension::dispExtendedDesc”). Directly writing the method name will not work :
Capture6
Form extensions are created pretty much the same way. The newly added methods are accessible from the form and its extensions. The ‘this’ object in your extension contains the element object (FormRun) of the extended form, so you can query for elements and data sources easily from here. Here is an example how to use element variables in a form extension class in various different ways :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[ExtensionOf( formstr( ExtensionDemoForm ))]
final class ExtensionDemoForm_Extension
{
 private boolean hasArgs;

 private void new()
 {}

 public boolean parmHasArgs(boolean _hasArgs = hasArgs)
 {
 hasArgs = _hasArgs;
 return hasArgs;
 }

 public boolean hasArgsRecord()
 {
 return (this.args().record()!=null);
 }

 [FormEventHandler(formStr(ExtensionDemoForm), FormEventType::Initialized)]
 public void ExtensionDemoForm_OnInitialized(xFormRun sender, FormEventArgs e)
 {
 this.parmHasArgs((this.args()!=null));
 }

}
Note that the event handler here does not have the ‘static’ keyword, but instead declared as a class instance method intentionally removing the static keyword from the definition. It is possible to declare some of the event handlers like that then you can use ‘this’ keyword to access methods and variables defined in the same class from a eventhandler (more on eventhandlers on Part3).
You can add a simple click method and test our extension method inside the form :

public void clicked()
{
super();
info(strFmt("Has args?: %1", element.parmHasArgs()));
info(strFmt("Has args record?: %1", element.hasArgsRecord()));
}


Capture7
Eventually it is also possible to override form control and data field methods from the form extension class, which gives you much more flexibility in creating extensions for forms:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[ExtensionOf( formstr( ExtensionDemoForm ))]
public final class ExtensionDemoForm_Extension
{
public void clickedOverride()
{
info(strFmt("Name : %1", this.name()));
}

[FormEventHandler(formStr(ExtensionDemoForm), FormEventType::Initialized)]
public void ExtensionDemoForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
FormButtonControl button = this.design().controlName(formControlStr(ExtensionDemoForm,FormButtonControl1));

button.registerOverrideMethod(methodStr(FormButtonControl,clicked),'clickedOverride', this);
}
}
A note on form class extensions: In this time of writing (Update 3 version), not everything with form class extensions work all right, it has some bugs which are already reported to Microsoft. For example the code above compiles ok but on runtime the method above is not accesible from the Formrun class, although the previous example compiles and runs just fine (just using ‘this’ keyword in the same class). I will post an update if things change in the future on that part.
Plugins
Plugins are yet another way to extend code in new AX7 besides the extension methods we mentioned above. Plugins use attributes to create new subclasses and subclass methods which can be used as alternatives to existing classes and methods belonging to same plugin base and can be initialized using a string type of key from the SysPluginFactory class. This way you can also choose which plugin to use by selecting it with a string parameter. I will not get into the details of using plugins and the attributes used in plugin framework but perhaps I might write another blog about them in the future.


3 comments:

Adding a newline into a string in C# and X++

Below is the sample code we can use for  adding a newline after every occurrence of "@" symbol in the string in C#   using System...