AX2012: SysOperation part 3: SysOperationAutomaticUIBuilder

August 24, 2011 at 12:00
filed under Dynamics AX
Tagged , , ,

Hi everyone.

This is the third and final part in my series of posts about SysOperation (at least for now). You can find part 1 here and part 2 here.

I will demonstrate how to override methods on the dialog (in this case modified()), and how to read and set properties of controls.

For the sake of this demo, we will add a checkbox to the dialog that will allow us to set the enabled property of the date control.
It should look like this:

1. Modify the data contract

We will need to modify the data contract so a checkbox is displayed on the dialog that we can use to enable or disable the date control.

Add this variable to the class declaration of the KlForCustTesterDataContract class:

    NoYesId     allowModifyDate;

Then add this method to that same data contract

[DataMemberAttribute,
SysOperationLabelAttribute("Enable date control")]
public NoYesId parmAllowModifyDate(NoYesId _allowModifyDate = allowModifyDate)
{
    allowModifyDate = _allowModifyDate;
    return allowModifyDate;
}

There’s nothing new here, we already did all of this in part 1

2. Extending SysOperationAutomaticUIBuilder

We will extend the SysOperationAutomaticUIBuilder class so we can override methods and properties of controls on our dialog.

First, create a new class, KlForCustTesterUIBuilder:

class KlForCustTesterUIBuilder extends SysOperationAutomaticUIBuilder
{
}

Things we will need in this class are:

  1. a variable for the date control
  2. a variable for the new checkbox control
  3. our data contract

So we add these variables to the class declaration:

class KlForCustTesterUIBuilder extends SysOperationAutomaticUIBuilder
{
    DialogField     dialogFieldAllowModifyDate;
    DialogField     dialogFieldTransDate;

    KlForCustTesterDataContract klForCustTesterDataContract;
}

2.1 Adding the override method

We will first write the method that will override the modified of the checkbox control. Simply add a new method to the KlForCustTesterUIBuilder class:

public boolean allowModifyDateModified(FormCheckBoxControl _checkBoxControl)
{
    ;
    // set enabled or disabled based on checkbox
    dialogFieldTransDate.enabled(any2enum(dialogFieldAllowModifyDate.value()));
    // or alternatively
    // dialogFieldTransDate.enabled(_checkBoxControl.checked());
   
    return true;
}

The code above sets the enabled method on the dialogFieldTransDate object based on the value property of dialogFieldAllowModifyDate object, or alternatively, the _checkBoxControl variable.
As you may remember, we declared the DialogField variables in the class declaration. Of course we will still have to initialize these variables , and that’s what we’ll do when overriding the postBuild method.

2.2 Overwriting the postBuild method

Next, we need to override the postBuild method on our KlForCustTesterUIBuilder class. This method will be called after the dialog is created, so it is a good place to put our logic.

Override the method:

public void postBuild()
{
    ;
    super();
}

Next, let’s add some code to this method, starting with the code that retrieves the data contract object, which is pretty easy:

public void postBuild()
{
    ;
    super();

    // get datacontract
    klForCustTesterDataContract = this.dataContractObject();
}

Next, we retrieve the DialogField objects:

public void postBuild()
{
    ;
    super();

    // get datacontract
    klForCustTesterDataContract = this.dataContractObject();

    // get dialog fields
    dialogFieldTransDate        = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmTransDate));
    dialogFieldAllowModifyDate  = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmAllowModifyDate));
}

In the code above, the bindInfo() method returns an object of type SysOperationUIBindInfo. This contains information about which dialog controls the data members are bound to. By providing a reference to the parmTransDate and parmAllowModifyDate member when calling the getDialogField() method, we get the dialog control that is associated with each member.

Next, we will register the method we want to override:

public void postBuild()
{
    ;
    super();

    // get datacontract
    klForCustTesterDataContract = this.dataContractObject();

    // get dialog fields
    dialogFieldTransDate        = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmTransDate));
    dialogFieldAllowModifyDate  = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmAllowModifyDate));

    // register override methods
    dialogFieldAllowModifyDate.registerOverrideMethod(methodstr(FormCheckBoxControl, modified), methodstr(KlForCustTesterUIBuilder, allowModifyDateModified), this);
}

As you can see, we can use the registerOverrideMethod() method to override methods on dialog. This is a huge improvement over overriding methods on dialogs in 2009. We simply point to the method we want to override (FormCheckBoxControl.modified) and the method the needs to be executed (KlForCustTesterUIBuilder.allowModifyDateModified).

Finally, we initialize the value of the enabled property, and the complete method will look like this:

public void postBuild()
{
    ;
    super();

    // get datacontract
    klForCustTesterDataContract = this.dataContractObject();

    // get dialog fields
    dialogFieldTransDate        = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmTransDate));
    dialogFieldAllowModifyDate  = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmAllowModifyDate));

    // register override methods
    dialogFieldAllowModifyDate.registerOverrideMethod(methodstr(FormCheckBoxControl, modified), methodstr(KlForCustTesterUIBuilder, allowModifyDateModified), this);

    // enable/disable transdate based on checkbox
    dialogFieldTransDate.enabled(any2enum(dialogFieldAllowModifyDate.value()));
}

3. One more attribute

Now, we’ve created the UIBuilder class, but we still have to link it to our data contract. That’s what the SysOperationContractProcessingAttribute attribute is for.

To link the UIBuilder class to the data contract, open the classDeclaration method of the KlForCustTesterDataContract class, and add the SysOperationContractProcessingAttribute:

[DataContractAttribute,
    SysOperationContractProcessingAttribute(classstr(KlForCustTesterUIBuilder))]
class KlForCustTesterDataContract
{
    Name        name;
    TransDate   transDate;
    str         packedQuery;

    NoYesId     allowModifyDate;
}

4. Testing!

But wait (you already know what I’m going to say right), first click the Generate Incremental CIL button to generate CIL.

Right click the KlForCustTesterServiceController menu item we created on day 2, and choose open. You should see this dialog:

When you check the checkbox, you should see that the date field is enabled.

Download the XPO for part 3 here.

That concludes my blog posts about SysOperation, I hope you like it, and I’m looking forward to your feedback.
Have fun making batches!

34 comments

RSS / trackback

  1. Kenny Saelen

    Nice post !

    I’ve made it past part 1 and 2 because I had seen this when creating a previous operation, but this one is new :-)

    I wandered how it was done, so now I know :-)

    greets,
    K

  2. DAX

    Hi!
    Yesterday I had asked you 2 questions, I am elaborating one question here in point 1.

    1: In runBase we can add Batch jobs and Batch tasks;We can select class name in the form by overriding some methods “cangobatchjournal”,”description” etc.. How we are going to do this in Business Operations Framework.

    2:If we have a scenario like the same logic we have to execute for
    a) inputs from the UI
    b) for batch which will run in background taking the parameters from some table

    3: How we are going to add “Default” button on the UI which will reset the values to default ?
    Is “parmShowDialog” approach can be used ?
    Class Sysoperationssevicecontroller/startoperations will call to parmshowdialog.

    Thanks,
    Bhasker

  3. Sandip

    Hi,

    I have a requirement for SSRS dialog event as described by you in this article. Have developed my UIbuilder and contract class same as described. But facing a strange issue in postBuild() method of UIBuilder class. The dialog field is always getting a null value through bindInfo() method and throwing stack trace error when report dialog is opened.

    Following is the code snippet:

    totalByPostingProfile = this.bindInfo().getDialogField(rdpContract, methodStr(CustAccountStatementCurrContract_FR, parmTotalByPostingfrofile));

    here variable totalByPostingProfile not been initialised and getting a null value.

    Can you please provide any suggestion ?

  4. Klaas Deforche

    Hi, Sandip,

    That’s strange. Did you call the super() method?
    Also, check if the parmTotalByPostingfrofile method is declared public.
    If you run the SysOperation withoud the UIBuilder, does everything work then? Are the fields on the dialog?

    Can you post the complete code of the method?

    If all else fails, do a compile forward on your classes, remove your auc file, restart your aos and compile CIL.
    I’ve noticed that some errors are due to AX behaving odd.

  5. Klaas Deforche

    Hi DAX,

    1. You can still do the “multithreading”. Check the class PurchFinalizeService for an example. I didn’t try it myself yet, on reason is that I don’t really like how they solved MS solved it. They still use RunBaseBatch classes for the tasks…

    2. In that case I think I would create two controller classes, one that takes input from the dialog, and one that reads the table and fills the datacontract. You can reuse the service so this shouldn’t take long.

    3. I’m affraid I can’t answer this one, I couldn’t find the default button option either. Normally when you press the default button, the initParmDefault method should be called.
    Check the SysOperationController class, methods initParmDefault and initializeParametersInternal. My guess is that it has something to do with contracts that implement SysOperationInitializable. But you still need a button to call the initParmDefault method… I’ll have to research that myself…

  6. Arshad Kalam

    Great Post!!!!!.

    Thank you so much….

  7. Kumar

    Hi,

    Thanks for the great post !

    I have a scenario where i need to have the default dialogField returned by the query(‘Customer account’ in your example) enabled/disabled depending on a checkbox.
    Basically, the Batch Job is supposed to run on different criterion(based on different ranges for the query) and the checkbox is merely intended to either use a base criteria or a any criteria(which is there by default).

    So, i tried to achieve the objective by following the exact procedure as shown here, only i’m trying to get the dialogField from the parmQuery :
    dialogFieldQuery = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmQuery));
    and subsequently enable/disable it based on the checkbox value.

    Unfortunately, it didn’t work for me and threw a ‘Dialog field object not initialized’ on the registerOverideMethod().

    Any insights ??

  8. Klaas Deforche

    Hi Kumar,

    I don’t that that’ll work, because the parmQuery is not mapped to a dialogfield. The dialogfields you see are because you added a range on the query.
    I think you’ll have to update or add a query range instead.
    If the checkbox is on, you add a range on the query (and set it as locked so it can’t be edited?). If the checkbox is off, allow users to specify their own range when the click on the select button.

    Does that make sense in your scenario?
    Hope it helps.

  9. Kumar

    Yup, that makes sense !

    Thanks !!

  10. Søren

    Hi, great tutorial.
    Is it possible to set any of your parms to be mandatory??
    E.g the Name field, so the user cannot continue before filling in something.

    It is easy enough to validate after they click ok, but a bit late in the flow I believe.

    /Søren

  11. Klaas Deforche

    Hi Søren,

    Yes it is possible. One way you can do it that I didn’t describe (because I didn’t know) is to implement the SysOperationValidatable interface on your data contract.
    Then implement the valdiate method on your data contract and put validation code there.

    Same for initializing parms, you can implement the SysOperationInitializable interface.

  12. Kishore

    Good JOb!!

    It really hepful to create a batch jobs.

  13. Christobaal

    Hello Klass (btw ‘Klass’ is a great name for an OO dev ^^)

    I just wanted to say a big ‘Thanks !’ for your tutorial.
    Even 2 years later, it’s really usefull ;)

    Keep up the good job !

    Cheers !

    JC

  14. Klaas Deforche

    It is fitting isn’t it :). Thanks for the kind words, I’m glad I could help.

  15. Beat Pallagi

    Hi Klaas,

    thnx 4 sharing this.

  16. MadsenSenior

    Hi Klaas, very nice post, I have done the programming and get 2 entries in the batchjob list, every time i run it, but my batch jobs is being repeted, when it has finished a new entry in the batchjob list is created with a new start time, what can I have done wrong?

  17. Søren Andersen

    Hi, is it possible to hide the General tab??
    If I only want the batch tab..

    Best regards
    Søren Andersen

  18. Klaas Deforche

    Hi Søren,

    I’ve tried but failed so far. I will look into it over the weekend.

    Options I’m thinking of:
    – using an other form template on the controleler
    – modifying the UI builder to hide the general tab (it works but you have to activate the batch tab afterwards)

    Best regards,
    Klaas.

  19. Albert Akuamoah

    Hi Søren,

    I just tried this and it worked as a work around. I simply activate the batch Tab. I have a class which extends SysOperationServiceController so I override the dialogPostRun and selected the batch tab.

    In SysOperationController dialogPOstRun, MSFT sets the active tab to be the General tab. You can leverage that code if you want a systemwide effect. (I wouldn’t recommend it though)

    protected void dialogPostRun()
    {
    sysOperationDialog sysOperationDialog;
    DialogTabPage batchTab;
    FormRun fr;

    super();

    sysOperationDialog = this.dialog() as SysOperationDialog;

    fr = sysOperationDialog.formRun();

    batchTab = sysOperationDialog.batchDialogTabPage();

    fr.selectControl(batchTab.control());
    }

    By the was Klaas, You do a wonderful job of sharing your AX knowledge. Thanks.

  20. Klaas Deforche

    Hi Albert, thank you for sharing your solution.
    I’m sure this will come in very handy :).

  21. Albert Ritmeester

    Thanks for the clear documentation!

    I have one question: I want to override the lookupReference for a FormReferenceControl. I cannot get it working. Do you have any idea how to achieve this?

    Thanks!

  22. John Shine

    Are you aware of a control that will submit a job without the need for the UI (batch dialog). We wish to use sysOperation to fire off a batch job without user input if possible.

  23. John Shine

    parmshowdialog(false)

  24. Ashish

    getting error dialog object not intialized please help

  25. Swapna

    Use this code in controller postRiun method to hide the general tab on the dialog. It worked for me.
    this.dialog().dialogForm().tab().showTabs(false);

  26. Preetham

    Hi,
    How can I get query object initialized in post build the similar way as dialog field?
    Thanks!

  27. Arpit

    @Søren : to remove the general tab, simply remove the contract parameter from the service class method.
    It worked for me.

    I have one query for all of you, it will be very helpful if you guys suggest me.

    my dialog takes a parameter as file from the local computer.
    So I used FileNameOpen edt to define my parmMethod in contract class, so that I will get the browse option on my dialog. The same edt we used when we were using RunBaseBatch.
    But when i try to open it, it doesnt show me browse button.

    Anyone ???

  28. John Hagler

    I’m running into an interesting scenario when using a template form. When I don’t override the SysOperationServiceController.templateForm() method and let the standard functions build the dialog UI, I get the dialog parameters put into a smaller section with scrollbars. One way I have attempted to get around this is to create a form and use it for the template form. This allows me to disallow scrollbars in the form so it displays correctly. The side effect is that the BatchInfo doesn’t recognize the batch checkbox. So I don’t seem able to send something to batch with a template form. This does work for the exact same class however if I let the standard UIBuilder handle the dialog. Any ideas what could be causing this?

  29. DM

    Hey Arpit,

    Controls are bound to using a data contract.
    So use a data contract with one of the attribute as FileNameOpen.

    To hide the general tab –
    In controller’s dialog post run method i wrote below code –

    protected void dialogPostRun()
    {
    sysOperationDialog sysOperationDialog;
    FormTabControl tabControl;
    FormRun formRun;
    #define.generalTabControlName(‘Tab’)

    super();

    sysOperationDialog = this.dialog() as SysOperationDialog;
    formRun = sysOperationDialog.formRun();
    tabControl = formRun.design().controlName(#generalTabControlName);
    tabControl.showTabs(false);

    }

    Give it a shot.

  30. Michele

    Do you know how I can get the filenameopen option to work with the SysOperationFramework? I have a parm method in my contract class with FileNameOpen EDT but still not seeing the file browser button when running the service?

  31. Andrew Xu

    Sandip,
    You’ll need to use SRSReportDataContractUIBuilder instead of SysOperationAutomaticUIBuilder, if you’re working on a SSRS report.

  32. Aleš

    Thanks for this nice article, very helpful for our Dynamics development

  33. Juan Ramirez

    Hello,
    In dynamics ax 2012 “The FormRun object could not be created”
    code:
    void GNP_VendInvoiceJourAddData_Clicked(FormButtonControl _formButtonControl)
    {
    DictTable dictTable;
    Form formAddData;
    FormBuildDataSource fbDatasource;
    FormRun formRun;
    Args args;
    ;
    dictTable = new DictTable(tableNum(GNP_VendInvoiceJourAddData));
    formAddData = new Form();
    formAddData.name(“GNP_VendInvoiceJourAddData”);

    fbDatasource = formAddData.addDataSource(dictTable.name());
    fbDatasource.table(dictTable.id());
    args = new Args();
    args.object(formAddData);
    formRun = classFactory.formRunClass(args);
    formRun.init();
    formRun.run();
    formRun.detach();
    }

    stop code in “FormRun formRunClass(xArgs args)”
    Please help.

  34. Ash

    Thnx for this breakdown. I’m new to X++ (and programming in general) I tried out all of the posts and in Dynamics 365 I can confirm that most is still the same. However, the only thing that does not seem to work the same is the date. The field is empty. Even when I enable it, I have to select it manually.

respond