Category: Development

  • Report Print Preview: “Go to main table” link for display method fields

    Situation:
    A report containing e.g. PurchParmLine.itemId() will not show the orange hyperlink in the print preview screen that allows the user to jump to the main table.

    Solution:
    To make add a link to a display method field on a report you must replace the display method field by a temporary table datasource. Populate the desired field and send the temporary datasource.

    public void executeSection //e.g. located in PurchParmLine body
    {
        InventTable tmpPrintItemId; //any table with the ItemId field will do
        ;
        tmpPrintItemId.ItemId = PurchParmLine.itemId();
        element.send(tmPrintItemId);
        super();
    }

    Now you can add a Field with Properties Table = Inventtable and DataField = ItemId, which will print the contents of PurchParmLine.itemId() and will have the orange hyperlink in the print preview that allows the user to jump to the main table.

    See also msdn How to

  • Get screen width, height etc. in X++ code

    I took me a bit longer than expected to Google the solution for this Dynamics AX X++ coding question, as I was did not have the keyword “system metrics” in mind. Maybe this post will help someone to be 2 minutes faster in finding the solution.

    static void test_WinAPI_getSystemMetrics(Args _args)
    {
        #WinAPI
        int screenWidth, screenHeight;
        ;
        screenWidth = WinAPI::getSystemMetrics(#SM_CXSCREEN);
        screenHeight = WinAPI::getSystemMetrics(#SM_CYSCREEN);
        info(strfmt("Screen resolution: %1 x %2",screenWidth, screenHeight));
    
        //other parameters:
        //SM_CXSCREEN = 0        ' Width of screen
        //SM_CYSCREEN = 1        ' Height of screen
        //SM_CXFULLSCREEN = 16   ' Width of window client area
        //SM_CYFULLSCREEN = 17   ' Height of window client area
        //SM_CYMENU = 15         ' Height of menu
        //SM_CYCAPTION = 4       ' Height of caption or title
        //SM_CXFRAME = 32        ' Width of window frame
        //SM_CYFRAME = 33        ' Height of window frame
        //SM_CXHSCROLL = 21      ' Width of arrow bitmap on
        //                       '  horizontal scroll bar
        //SM_CYHSCROLL = 3       ' Height of arrow bitmap on
        //                       '  horizontal scroll bar
        //SM_CXVSCROLL = 2       ' Width of arrow bitmap on
        //                       '  vertical scroll bar
        //SM_CYVSCROLL = 20      ' Height of arrow bitmap on
        //                       '  vertical scroll bar
        //SM_CXSIZE = 30         ' Width of bitmaps in title bar
        //SM_CYSIZE = 31         ' Height of bitmaps in title bar
        //SM_CXCURSOR = 13       ' Width of cursor
        //SM_CYCURSOR = 14       ' Height of cursor
        //SM_CXBORDER = 5        ' Width of window frame that cannot
        //                       '  be sized
        //SM_CYBORDER = 6        ' Height of window frame that cannot
        //                       '  be sized
        //SM_CXDOUBLECLICK = 36  ' Width of rectangle around the
        //                       '  location of the first click. The
        //                       '  second click must occur in the
        //                       '  same rectangular location.
        //SM_CYDOUBLECLICK = 37  ' Height of rectangle around the
        //                       '  location of the first click. The
        //                       '  second click must occur in the
        //                       '  same rectangular location.
        //SM_CXDLGFRAME = 7      ' Width of dialog frame window
        //SM_CYDLGFRAME = 8      ' Height of dialog frame window
        //SM_CXICON = 11         ' Width of icon
        //SM_CYICON = 12         ' Height of icon
        //SM_CXICONSPACING = 38  ' Width of rectangles the system
        //                       ' uses to position tiled icons
        //SM_CYICONSPACING = 39  ' Height of rectangles the system
        //                       ' uses to position tiled icons
        //SM_CXMIN = 28          ' Minimum width of window
        //SM_CYMIN = 29          ' Minimum height of window
        //SM_CXMINTRACK = 34     ' Minimum tracking width of window
        //SM_CYMINTRACK = 35     ' Minimum tracking height of window
        //SM_CXHTHUMB = 10       ' Width of scroll box (thumb) on
        //                       '  horizontal scroll bar
        //SM_CYVTHUMB = 9        ' Width of scroll box (thumb) on
        //                       '  vertical scroll bar
        //SM_DBCSENABLED = 42    ' Returns a non-zero if the current
        //                       '  Windows version uses double-byte
        //                       '  characters, otherwise returns
        //                       '  zero
        //SM_DEBUG = 22          ' Returns non-zero if the Windows
        //                       '  version is a debugging version
        //SM_MENUDROPALIGNMENT = 40
        //                       ' Alignment of pop-up menus. If zero,
        //                       '  left side is aligned with
        //                       '  corresponding left side of menu-
        //                       '  bar item. If non-zero, left side
        //                       '  is aligned with right side of
        //                       '  corresponding menu bar item
        //SM_MOUSEPRESENT = 19   ' Non-zero if mouse hardware is
        //                       '  installed
        //SM_PENWINDOWS = 41     ' Handle of Pen Windows dynamic link
        //                       '  library if Pen Windows is
        //                       '  installed
        //SM_SWAPBUTTON = 23     ' Non-zero if the left and right
        //                       ' mouse buttons are swapped
    }
    
  • Set up a new number sequence (e.g. for Blanket Orders)

    1. New Data Type
    2. Add a new SalesParameters method:

      static client server NumberSequenceReference  numRefSalesIdBlanket()
      {
          return NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(SalesIdBlanket)));
      }

    3. Assign SalesId in the somewhere during the creation of the blanket order:

      salesTable.SalesId = NumberSeq::newGetNum(SalesParameters::numRefSalesIdBlanket()).num();

    4. Add new reference to NumberSeqReference_SalesOrder class (used to add the new number sequence to the list in the CustParameters form)

      protected void loadModule()
      {
          NumberSequenceReference numRef;
          ;
      //blanket order number sequence
          numRef.DataTypeId              = typeId2ExtendedTypeId(typeid(SalesIdBlanket));
          numRef.ReferenceHelp           = literalstr("@SYS53960");
          numRef.ReferenceLabel          = literalstr("@CUS525");
          numRef.WizardManual            = NoYes::No;
          numRef.WizardAllowChangeDown   = NoYes::No;
          numRef.WizardAllowChangeUp     = NoYes::No;
          numRef.SortField               = 1;
          this.create(numRef);
          …
      }

    5. Set up the new number sequence in the customer parameters form.

    Done.

  • Dialogs

    I read in some other blog how to apply customer methods such as lookup() to a standard dialog. The trick is to apply a Field Identifier to the dialog field, and then create a method that follows the naming convention so it will be found at runtime. See the example below:

    A)

    class myClass extends RunBase
    {
        Dialog              dialog;
        #define.FID_OPPORTUNITYID(903)
    }

    // Add fields to the dialog
    protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
    {
        DialogRunbase  dialog = super();
        DialogGroup     dialogGroup;
        ;
        dialogGroup = dialog.addGroup("@SYS102068");
        dlgFldOpportunityId = new DialogField(dialog, typeid(smmOpportunityId), #FID_OPPORTUNITYID);
        dialog.addCtrlDialogField(dlgFldOpportunityId.name());
        dlgFldOpportunityId.init(dialog);
        dlgFldOpportunityId.value(opportunityId);
        return dialog;
    }

    //prefiltered lookup for opportunityId
    private void fld903_1_lookup()
    {
        FormStringControl control = dialog.formRun().controlCallingMethod();
        SysTableLookup    sysTableLookup = SysTableLookup::newParameters(tablenum(smmOpportunityTable), control);
        Query             query = new Query() ;
        QueryBuildDataSource    qbds;
        ;
        sysTableLookup.addLookupfield(fieldnum(smmOpportunityTable, OpportunityId));
        sysTableLookup.addLookupfield(fieldnum(smmOpportunityTable, Subject));
        qbds = query.addDataSource(tablenum(smmOpportunityTable));
        if(partyId)
            SysQuery::findOrCreateRange(qbds,fieldnum(smmOpportunityTable, PartyId)).value(partyId);
        sysTableLookup.parmQuery(query);
        sysTableLookup.performFormLookup();
    }

    This is very neat if you need to enhance only one special field, however I quickly noticed, that when you start improving the user interface, you will add the next function and the next and so on. Then the trick above is not the way to go. It is much easier to add a customer dialog as follows:

    B)

    class myClass extends RunBase
    {
        Dialog              dialog;
    }

    protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
    {
        DialogGroup dialogGroup;
        ;
        //dialog = super();
        dialog = Dialog::newFormnameRunbase(formstr(CustomDialog),this);
        dialog.caption(Runbase::getDescription(classidget(this)));
        dialogGroup = dialog.addGroup("@SYS102068");
        //add custom fields, without special functionality

        return dialog;
    }

    The corresponding form needs to have the following groups and buttons to work. In addition to those groups you can add various other fields, datasources etc. with all the handlers – such as lookup() – you like. You can pass the results by extracting myClass with element.args().caller().runbase() and implementing some parm methods on myClass, which you can access in edit methods.

    //edit methods can be drag and dropped into the design section
    edit ItemId itemId(boolean _set, ItemId _itemId)
    {
        ItemId ret;
        ;
        if(_set)
        {
            myClass.parmItemId(_itemId);
        }
        ret = myClass.parmItemId();
        return ret;
    }

    Thomas

  • Links in Infolog

    Just a short reminder for myself.

    If you want your info text ( or warning or error) to link to a record, then you can just add a SysInfoAction_TableField class to the call:

    info(“info text”, “help text”, SysInfoAction_TableField::newBuffer(salesTable));

    But if there is the link is not defined by the record e.g. in the case of returns, you need to use the SysInfoAction_FormRun class:

    SysInfoAction_FormRun sysInfoAction_FormRun;
    ;
    sysInfoAction_FormRun = SysInfoAction_FormRun::newFormname(formstr(ReturnTable));
    sysInfoAction_FormRun.parmCallerBuffer(salesTable);
    info(“info text”, “help text”, sysInfoAction_FormRun);

  • Multilingual Records using Label editor

    Situation

    As I needed multilingual data on a custom table I tried to identify if Microsoft has a best practice it applies to this requirement. I had to find out that multilingual data is handled differently in every instance I identified.

    The application itself (forms, buttons, instructions, help etc.) is truly multilingual and is handled in a uniform throughout AX. On the other hand, record data mostly cannot be saved in different languages. At the most important places Microsoft has recognised this and has added the possibility of adding multilingual data.

    Below are all the instances of multilingual texts I have identified:

    Label files (application text):

    This is still pretty straight forward: Translations of application text is handled in one location (the label files). There is one editor for editing the label files.

    • X++: The label code is in quotation marks (e.g. "@SYS1234") and the texts can be edited in the label editor using context menu "Lookup Label/Text"
    • AOT object properties: Field showing the standard language, when the field is selected the label code and "…" are displayed. A click on the three dots leads you to the label editor as above.
       
    • The Rename Item Dimension dialogue is the exception to the rule. The dialogue bypasses the label editor, allowing the user to select a language and then edit multiple labels at a time.

    Separate tables (multilingual data):

    Data is always handled in separate tables and never using label files. I guess this is because you do not want the label files to grow indefinitely as more and more data is added. I wonder however if it could have been an option to use labels for a relatively limited table such as the unit table.

    • Item text: Translations for the item text are saved in the InventTxt table. The item text is NOT saved on the InventTable, the default value that is displayed corresponds to the company language. If you change the company language or you share the table over multiple companies with different languages, the default text might be missing. The foreign key linking the InventTxt table to the InventTable is the ItemId.
    • Unit text: Translations of the unit text are saved in the UnitTxt table, the default text is however saved on the Units table. The foreign key linking the UnitTxt table to the Unit table is the UnitId.
      Even though there is essentially no difference in the table definitions of UnitTxt and InventTxt, the user experience is not the same.
    • Workflow instructions: The multilingual instructions of Workflows are again implemented in a different way: The WorkFlowMessageTxt table is a bit more generic than the previous examples by using the TableId and RecordId to link to the source table. In addition the Multilanguage entry form allows the use of variables in the text. Just as is the case with the Item text, the Message text is not save on the base table at all. The system first tries to fetch the users language, if not found, then it tries to fetch the company language. The problem of the item text remains: if you change the company language or you share the table over multiple companies with different languages then the default text might be missing.
       

     

    Suggestion for Microsoft

    Just as Microsoft has added the UtcTimeDate type that supplies clever editing of date and time, Microsoft should add a new datatype for multilingual strings ("MLString"), that supplies the interface to edit, save and display the translations.
    I would like to see the following properties:

    • Default value when used in forms and reports: Try to find a language specific value in the following order 1. text in users language, 2. text in variant of users language (e.g. en-us instead of en-au), 3. text in company language, 4. text in variant of company language (e.g. de instead of de-ch) 5. any language
    • Possibility of adding a second data field or a static text to define a specific language. Optional: If this language is not found, then follow the rules above.
    • One consistent way of editing the translations. Suggestion: use the ItemName type of editing.
    • Transparent management of the saved translations; Suggestion: A table contains all translations with the key TranslationId (preferably Int64 instead of a string) and the LanguageId. Maybe the TableId of the referencing table could be added to allow horizontal partitioning if the table grew too large. All that needs to be saved in the referring table is the TranslationId.

     

    UtcTimeDate:

  • Show drop-down list when entering a non-existent item

    Goal:
    • A user can get a dropdown list, just by typing a few letters and pressing enter. (Instead of entering a * to trigger the drop down list or instead of using the mouse)

    Solution:

    • The following static method can be called from the overridden task method of any form. The current implementation only supports ItemId and RouteOpr field. (It would be great if this code were generalized by someone.)
    • NOTE: relies on field name

    //bw start
    //Changed on 16 Jul 2007 by TW
    /* Description:
    display dropdown list if enter clicked while an itemid is not valid
    */
    static public int task(Args args, int _taskId)
    {
        #MACROLIB.Task //import task macro
        FormControl fc;
        FormStringControl fsc;

        switch(_taskId)
        {
            case #taskEnter: //enter key pressed
                fc = args.caller().selectedControl();
                if (SysFormRun::controlType(classidget(fc)) == FormControlType::String)
                {
                    fsc = fc;

                    if (fsc.name() like "*" + fieldid2name(tablenum(InventTable),fieldnum(InventTable,ItemId)) + "*")
                    { //ItemId fields
                        if (!InventTable::exist(fsc.text()))
                        {
                            fsc.lookup();
                            return 1;
                        }
                    }
                    else if (fsc.name() like "*" + fieldid2name(tablenum(InventTable),fieldnum(Route,OprId)) + "*")
                    { //RouteOpr Fields
                        if (!RouteOprTable::exist(fsc.text()))
                        {
                            fsc.lookup();
                            return 1;
                        }
                    }
                }
                break;
        }
        return 0;
    }
    //bw end