Showing posts with label form control. Show all posts
Showing posts with label form control. Show all posts

Monday, April 4, 2022

How to resolve reference with a form control value

If you need not just to maintain lookup for a refence group but also to validate a manually input value, you need to implement resolveReference method for the field of the form data source. Say, we want to validate a custom financial dimension value.





You can check resolveReference* methods in EcoResCategory table as a good example.


The most interesting detail for me is the way how the related form control value is found inside of the given reference group.

            /// <summary>
            /// Resolve reference
            /// </summary>
            /// <param name = "_formReferenceControl"></param>
            /// <returns></returns>
            public Common resolveReference(FormReferenceControl _formReferenceControl)
            {
                Common ret;
            
                ret = myAssignedBankAccountDimension.dimensionResolveReference(_formReferenceControl);
            
                return ret;
            }

    public Common dimensionResolveReference(FormReferenceControl _formReferenceControl)
    {
        DimensionAttribute                          dimensionAttribute;
        DimensionAttributeDirCategory               dimAttributeDirCategory;
        DimensionFinancialTag                       dimensionFinancialTag;
        myFinancialDimensionValueFinancialTagView  view;
        myAssignedBankAccountDimension             myAssignedBankAccountDimension;
        DimensionDisplayValue                       dimensionDisplayValue;

        if (!_formReferenceControl || _formReferenceControl.handle() != classNum(FormReferenceGroupControl) )
        {
            throw(error(strFmt("@SYS137393", Error::wrongUseOfFunction(funcName())) ));
        }

        dimensionDisplayValue = _formReferenceControl.filterValue(AbsoluteFieldBinding::construct(fieldStr(DimensionFinancialTag, Value), tableStr(DimensionFinancialTag))).value();
        dimensionDisplayValue = strLRTrim(dimensionDisplayValue);
        
        <..  implement your logic with the display value ...>

Thursday, March 17, 2022

Lookup, JumpRef, Modified for a form data source field through CoC

 As  explained in his old article https://community.dynamics.com/365/financeandoperations/b/ievgensaxblog/posts/d365foe-how-to-override-form-data-source-field-lookup-method, it is much better to override methods directly on a data source field than on its linked form controls.

I just want to re-iterate it and place here code snippets.


  • User can add new control using form personalization and this control won’t support overridden logic. It could be critical if you are restricting field lookup values or adding validations.
  • One form could have several controls that refers to one data source field so you have to duplicate your code.
  • Number of delegates are limited as well.
So, say we need to implement Lookup, JumpRef, and Modified methods for a custom field on CustInvoiceTable data source of CustFreeInvoice form. Implement these three aforementioned methods, e.g., directly in an extension to the form class.

 
[ExtensionOf(formStr(CustFreeInvoice))]
final class myCustFreeInvoice_Form_Extension
{
    public void myAssignedBankAccountIdModified(FormDataObject _targetField)
    {
       <logic>
    }
    public void myAssignedBankAccountIdJumpRef(FormDataObject _targetField)
    {
       <logic>
}
    // Different parameter here!
    public void myAssignedBankAccountIdLookup(FormStringControl _callingControl)
    {
       <logic>
    }

Now simply override them for the field when the data source is initialized.
 
public class myCustFreeInvoice_Form_EventHandler
{
    [FormDataSourceEventHandler(formDataSourceStr(CustFreeInvoice, CustInvoiceTable), FormDataSourceEventType::Initialized)]
    public static void myCustInvoiceTable_OnInitialized(FormDataSource _sender, FormDataSourceEventArgs _e)
    {
        FormRun         eogFormRun                  = _sender.formRun();
        FormDataObject  eogCustomField              = _sender.object(fieldNum(CustInvoiceTable, eogCustomField));
fdoEOGAssignedBankAccountId.registerOverrideMethod(methodStr(FormDataObject, jumpRef), formMethodStr(CustFreeInvoice, myAssignedBankAccountIdJumpRef), myFormRun); fdoEOGAssignedBankAccountId.registerOverrideMethod(methodStr(FormDataObject, lookup), formMethodStr(CustFreeInvoice, myAssignedBankAccountIdLookup), myFormRun); fdoEOGAssignedBankAccountId.registerOverrideMethod(methodStr(FormDataObject, modified), formMethodStr(CustFreeInvoice, myAssignedBankAccountIdModified), myFormRun); }

Thursday, February 10, 2022

Overload a method for a new button in Form extension

 This can be used as a copy-paste pattern, when you need to change a standard method for new form controls added as a form extensions.

At the form initialization step we can overload any form control methods with registerOverrideMethod.

[ExtensionOf(formStr(<FormName>))]
public final class my<FormName>_MyNewButton_Extension
{
   public void init()
    {
        next init();
		// MyNewButton button added in an extension to <FormName>
        FormButtonControl myButton = this.design().controlName(formControlStr(<FormName>, myNewButton));
		// Here we can overload its standard clicked() method in run-time		
        myButton.registerOverrideMethod(methodStr(FormButtonControl, clicked), formMethodStr(<FormName>, myNewButtonClicked), this);
    }

    public void myNewButtonClicked(FormButtonControl _sender)
    {
		<run some logic>
        _sender.clicked();
    }
}

Check this article for more complicated scenario https://alexvoy.blogspot.com/2018/09/lookup-and-modified-methods-for.html

Wednesday, July 8, 2020

Event handler: Get access from FormDataSource argument to other data sources and form controls

Kind a code template to accelerate our job:


    [FormDataSourceEventHandler(formDataSourceStr(<FormName>, <FormDataSourceName>), FormDataSourceEventType::Activated)]
    public static void FormDataSourceName_OnActivated(FormDataSource _sender, FormDataSourceEventArgs _e)
    {
        <FormDataSourceTable>       formDataSourceTable                 = _sender.cursor();
        FormRun                     formRun                             = _sender.formRun();
        FormDataSource              anyFormDataSource_ds                = formRun.dataSource(formDataSourceStr(<FormName>, <AnyFormDataSourceName>)) as FormDataSource;
        <AnyFormDataSourceTable>    anyFormDataSourceTable              = anyFormDataSource_ds.cursor();
        FormControl                 anyFormControl                      = formRun.design(0).controlName('AnyFormControlName');
        
        // your logic goes here, for example
        if(formDataSourceTable.enabled())
        {
            anyFormControl.visible(false);
            anyFormControl.enabled(!anyFormDataSourceTable.RecId);
        }
    }

Wednesday, April 22, 2020

New Image in FormGroupControl with BusinessCard Extended style

Developing and customizing forms in D365 are limited by predefined patterns and styles.

We can however overcome these limitations to some extent by placing new form controls and changing properties of existing ones and, of course, a bit of coding.

As an example, let's add a new Workflow image similar to Expense category to be shown in BusinessCard form group control, once an expense is assigned to the current user.



There are multiple ways to achieve the required change. To mention a few, playing with Style and ExtendedStyle properies in design, changing form controls placement with Top, Bottom, Left, Right properties, playing with DisplayOptions at run time, combining both images into one, replacing the standard images to customized ones: one for Assigned-to-me Category and standard Category, and so one.





Here we consider adding a new image of Workflow icon next to the standard Category one.We have a display method returning the required image. The key point here is to set its ExtendedStyle property to card_imageSquare, so that it would be shown properly.



Now it looks almost perfect, but the new form control pushed a bit the currency amount out of the card frame. 



Let's fix it by hiding the standard form control and placing its duplicate with ExtendedStyle = None.



The last thing is to make the text bold in order to emphasize it.


[ExtensionOf(tableStr(TrvExpTrans))]
final class TrvExpTrans_Extension
{
    boolean isCurrentUserWorkflow()
    {
        ...
    }

    display container currentUserWorkflowIndicator()
    {
        ImageReference imgRef;
        if (this.ApprovalStatus == TrvAppStatus::Pending && this.isCurrentUserWorkflow())
        {
            imgRef = ImageReference::constructForSymbol(ImageReferenceSymbol::Workflow);
            return imgRef.pack();
        }

        return conNull();
    }

    [FormDataSourceEventHandler(formDataSourceStr(TrvExpenses, TrvExpTrans), FormDataSourceEventType::DisplayOptionInitialize)]
    public static void ds_OnDisplayOptionInitialize(FormDataSource sender, FormDataSourceEventArgs e)
    {
        FormDataSourceDisplayOptionInitializeEventArgs  eventArgs   = e as FormDataSourceDisplayOptionInitializeEventArgs;

        FormDesign                                      fd          = sender.formRun().design(0);
        FormRowDisplayOption                            fo          = eventArgs.displayOption();
        FormControl                                     fc          = fd.controlName("newAmountCurrWithCurrencyCode");
        // if we can find our new form control for the expense amount
        if(fo && fc)
        {
            // let's make it bold to emphasize
            fo.affectedElementsByControl(fc.id());
            fo.fontBold(true);
        }
    }

}

The final view.


Tuesday, March 10, 2020

Data validation by ValidateField and ValidateWrite (old but good)

The most time data are meant to be changed by a user via different kinds of forms or even without it, and sometimes we need to implement additional business logic there.

Say, we want that a discount never be more than 10%, while a user creates lines in a sales order. For such a scenario the best way is to implement ValidateField and check the value for the field of Discount. After that the form does not allow the user to leave the field of discount until its value is less or equal to 10%.


public boolean validateField(FieldId _fieldIdToCheck)
{
    boolean ret;

    ret = super(_fieldIdToCheck);
    switch(_fieldIdToCheck)
    {
        case fieldNum(MySalesLine, Discount):
            if(this.Discount <= 10)
            {
               ret = true;
            }
            else
            {
                ret = checkFailed("Discount cannot be more than 10%");
            }
            break;
    }

    return ret;


But we should remember that ValidateField method is called automatically for a form data source field only.



In other words, if the table record is inserted or updated by code, this logic will be NOT triggered.



[Form]
[DataSource]
     [DataField]
            public boolean validate()
    [Table]
     public boolean validateField(FieldId _fieldIdToCheck)


So, if we need to implement validation logic for the whole record, which is supposed to be triggered every time Insert or Update is called (not for form data sources only), we go with ValidateWrite.






Thanks Mohamed for this brilliant article Microsoft dynamics ax2012 : forms and tables methods call sequences, How To? Watch this presentation! It contains much more very useful information.



Monday, October 17, 2016

Restrict access to group form control in the grid

Basically to restrict access to a field group it is enough just to set its NeededPermission property, say, to Manual and then you can provide your users with a special privilege on this form control.

However, when it comes to a grid, it is not enough: you also need to change the same property and needed permission on all included fields or, like in my scenario, display methods.



Some helpful articles

Security Permissions Properties for a Form

How to restrict access to a button


Thursday, January 16, 2014

How to change Batch caption dialog field in run time. RunBaseBatch sample

This is an example of a RunBaseBatch class that demonstrates how to change another dialog fields in run time.

Let's say we have a parameter of enum type, which selects the right business logic inside of Run method.

It is a good idea to change the main static text of the dialog as well as the batch caption that serves as a description for an eventual batch job (and tasks).

After adding this field in Dialog method we need to override Modified method for this field.


protected Object dialog()
{
    FormComboBoxControl  combobox;
    dlg                 = super();

    dialogEventType = dlg.addFieldValue(EnumStr(uapInterfaceEventType), eventType );
    // to add details to the caption and task description in batch tasks
    dialogEventType.registerOverrideMethod(methodstr(FormStringControl, modified), methodstr(tmxRunBaseBatchSample, eventType_modified), this);
    // to avoid user input in the field
    combobox = dialogEventType.control();
    combobox.comboType(1);

    return dlg;
}

In eventType_modified method we call two additional methods to apply the user input respectively for BatchCaption and MainInstruction fields.

private boolean eventType_modified(FormStringControl _control)
{
    boolean ret = _control.modified();
    if(ret)
    {
        this.setBatchCaption();
        this.setMainInstruction();
    }
    return ret;
}

To get access to these dialog fields we use two different approaches. We find BatchCaption form control recursively inside of batch tab page based on its type.

private void setBatchCaption()
{
    FormStringControl                  batchCaptionControl;

    // to get the batch caption control; any of them if many
    batchCaptionControl = this.getBatchCaptionControl(dlg);

    if (batchCaptionControl)
    {
        batchCaptionControl.text(this.caption());
    }
}
// returns the batch caption form control if any in the dialog
private FormStringControl getBatchCaptionControl(DialogRunbase _dialog)
{
    FormStringControl                  batchCaptionControl;

    // recursive routine to look for the right form control of BatchCaption EDT
    Object findBatchCaptionControl(Object _parentObject)
    {
        int         i;
        Object      childControl;
        Object      foundControl;


        for (i = 1; i <= _parentObject.controlCount(); i++)
        {
            childControl = _parentObject.controlNum( i );

            // this is our boy
            if( childControl is FormStringControl && childControl.extendedDataType() ==  extendedTypeNum(BatchCaption))
            {
                // time to get up
                return childControl;
            }
            else
            {
                if (childControl is FormGroupControl)
                {
                    foundControl = findBatchCaptionControl(childControl);
                    if (foundControl)
                    {
                        return foundControl;
                    }
                }
            }
        }
        // just step back to check others
        return null;
    }
/////// main routine  /////////////////////////////////////////////////////////////
    if( _dialog && _dialog.batchDialogTabPage())
    {
        batchCaptionControl = findBatchCaptionControl(_dialog.batchDialogTabPage().control());
    }

    return batchCaptionControl;
}

As to MainInstruction we get it by its name.

private void setMainInstruction()
{
    FormStaticTextControl   mainInstructionControl;
    FormGroupControl        formGroup = dlg.mainFormGroup();

    // to get the main instuction static text of the dialog
    mainInstructionControl = dlg.dialogForm().runControl('MainInstruction');

    if (mainInstructionControl)
    {
        mainInstructionControl.text(this.caption());
    }
}

Now when the user changes the event type, two other fields change respectively.




You can find the whole project here.

How to set properties for the Reference Group form control from code

There is a small issue in AX 2012 with getting access to the siblings' properties of Reference Group form control - they are unavailable in AOT.

However, you still can get access to it from code during run time.

Let's say you have placed on your form a field named FilterCategory, which is a reference group, and you want to set its sibling FilterCategory_Name width to Column width value. As you can see there is no way to do that in AOT.



So you just create a method supposed to be called in the form init():

void setColumnWidthForFilterCategory()
{
    int                                 i;
    Object                              childControl;

    for (i = 1; i <= FilterCategory.controlCount(); i++) // FilterCategory is of FormReferenceGroupControl type
    {
        childControl = FilterCategory.controlNum( i );
        childControl.width( 0, FormWidth::ColumnWidth );
    }
}

And you get it!