Tuesday, December 30, 2014

Batch task progress on server for AX 2009

I already explained how to update progress for batch tasking in AX 2012. Now, it's time for AX2009.

You can simply change new() method in SysOperationProgressServer class as follows:


public void new(int _numberOfBars = 1, boolean _bypass=false)


Evidently it will increase the total execution time of a batch because SysProgress table will be updated for every task as many times as you call incCount(), setCount() etc.

So, it is worth changing construct method in RunbaseProgress class, so that the developer could decide to update progress or not.


Happy New Year!

Friday, November 21, 2014

How to print labels in multiple languages

This is a short job to print labels in English US and English Canada. The prefix defines the label file.

static void printLabels(Args _args)
{
    int     labelNum = 1;
    int     labelIndex;
    int     labelMaxNum = 216;
    str     labelPrefix = "@CGI";
    str     labelStr;
    str     languageIdEN = 'en-us';
    str     languageIdFR = 'en-ca';

    for (labelIndex = labelNum; labelIndex <= labelMaxNum; labelIndex++)
    {
        labelStr = labelPrefix + int2str(labelIndex);
        labelStr = strFmt("%1 : %2 /// %3", labelStr,
                                            SysLabel::labelId2String2(strFmt("%1", labelStr), languageIdEN),
                                            SysLabel::labelId2String2(strFmt("%1", labelStr), languageIdFR));
        info( labelStr);
    }
}

Tuesday, November 11, 2014

How to convert a wrong date

Standard str2date function tries to fix a wrong date. For example, if we got as a parameter fantastic June 31th, it returns us June 30, which can be a bad result for your business case.



There are at least two possible solutions for that.

First, by converting the result back to a string and comparing it with the initial string.

private container cgiValidateDate(str _dateStr)
{
    date                retDate     = str2Date(_dateStr, #cgiDateFormat);
    boolean             isOK        = true;
    str                 madeDateStr = date2StrUsr(retDate, DateFlags::FormatAll);

    if(retDate == dateNull() || _dateStr != madeDateStr)
    {
        error(strFmt("Date %1 is incorrect", _dateStr));
        isOK = false;
    }

    return [isOK, retDate];
}


Second, by using .Net function tryParse.


private container cgiValidateDate(str _dateStr)
{
    date                retDate;
    utcDateTime         retDateTime;
    boolean             isOK        = true;
    
    if(!System.DateTime::TryParse(_dateStr, byref retDateTime))
    {
        error(strFmt("Date %1 is incorrect", _dateStr));
        isOK = false;
    }
    else
    {
        retDate = DateTimeUtil::date(retDateTime);
    }
    return [isOK, retDate];
}


Happy date converting!

Wednesday, November 5, 2014

Dialog field with multiple choice

This is a small tutorial class on how to work with the new AX 2012 SysLookupMultiSelectCtrl class.

There are good examples on the internet as well as a tutorial class in AX 2012 itself but I want to explain how to pack/unpack values for this field and make it mandatory.

Let's say we need to select multiple sites in the dialog.



I hope my comments inline will be enough. If not let me know, please.


class tmxMultiSelectSiteTutorial extends RunBase
{
    FormBuildStringControl      fbscMultiSite;
    FormStringControl           fscMultiSite;
    container                   siteIds;
    str                         siteIdsStr;

    SysLookupMultiSelectCtrl    multiSiteCtrl;
    DialogRunbase               dialog;

    #define.CurrentVersion(1)
    #define.Version1(1)
    #localmacro.CurrentList
        siteIdsStr
    #endmacro
}



protected Object dialog()
{
    dialog = super();
    // add a new form build control for multiple choice; string type
    fbscMultiSite        = dialog.curFormGroup().addControl(FormControlType::String, identifierstr(AnyFormControlNameYouLike));
    fbscMultiSite.label('Site');

    return dialog;
}





public void dialogPostRun(DialogRunbase _dialog)
{
    FormRun formRun;

    super(dialog);

    formRun = _dialog.dialogForm().formRun();

    if (formRun)
    {
        // to get the access to the form control we created on the dialog
        fscMultiSite = formRun.design().control(fbscMultiSite.id());
        // create multiple loookup of SysLookupMultiSelectCtrl type
        // cgiInventSite query must exist in AOT; simply SiteId and Name from InventSite table
        multiSiteCtrl = SysLookupMultiSelectCtrl::construct(formRun, fscMultiSite, querystr(cgiInventSite));
        // to underline it red; actually it does not validate; so check the Validate method
        multiSiteCtrl.setMandatory(true);
        // if we restored from last values
        if(siteIdsStr)
        {
            //then we convert the string to container
            siteIds = str2con(siteIdsStr);
            // after create the special container of SiteIds and Names
            multiSiteCtrl.set(this.siteIds2Names(siteIds));
        }
    }
}





public boolean getFromDialog()
{
    if (multiSiteCtrl)
    {
        // selected sites convert to container of RecIds
        siteIds     = multiSiteCtrl.get();
        // convert it to string for pack/unpack
        siteIdsStr  = con2Str(siteIds);
    }
    return super();
}





private container siteIds2Names(container _c)
{
    InventSite      inventSite;
    container       contSiteId, contRecId;
    int             i, cLen = conLen(_c);

    for (i = 1 ; i <= cLen ; i++)
    {
        inventSite = inventSite::findRecId(conPeek(_c, i));
        // this part will be visible
        contSiteId += [inventSite.SiteId];
        // this part will be used by SysLookupMultiSelectCtrl as invisible
        contRecId  += [inventSite.RecId];
    }
    return [contRecId, contSiteId];
}





public boolean validate(Object _calledFrom = null)
{
    boolean ret;

    ret = super(_calledFrom);

    if(!conPeek(siteIds, 1))
    {
         ret = checkFailed('Site must be selected!');
    }

    return ret;
}





public void run()
{
    InventSite  inventSite;
    int         i;
    int         conNum = conLen(siteIds);

    // any business logic for the selected sites
    for( i = 1; i<=conNum; i++)
    {
        inventSite = inventSite::findRecId(conPeek(siteIds, i));
        info(strFmt("Site: %1 - %2", inventSite.SiteId, inventSite.Name));
    }
}






Labels and Best Practices




Guys, please, do not forget that AX is really multilingual system and there are many other languages than English. Happy labelling!

Monday, October 27, 2014

Find Price and Not Lose It

A system bug in PriceDisc class that leads to losing the found price/discount. Still presented in AX 2012 R3 (Application version 6.3.164.0) The local subroutine findDisc in findDisc method is called inside the buffer loop and must be fixed as follows.
void findDisc()
    {

        if ((discDate >= localFromDate  || ! localFromDate)
            && (discDate <= localToDate || ! localToDate))
        {
            if (_relation == PriceType::EndDiscPurch ||
                _relation == PriceType::EndDiscSales )
            {
                // for end discounts, the QuantiyAmountField field contains order total amounts, not quantities
                if (this.calcCur2CurPriceAmount(localQuantityAmountFrom, priceDiscTable) <= qty &&
                    ((qty < this.calcCur2CurPriceAmount(localQuantityAmountTo, priceDiscTable)) || !localQuantityAmountTo))
                {
                    reselectBuffer();

                    discExist               = true;
                    discAmount             += this.calcCur2CurPriceAmount(priceDiscTable.Amount, priceDiscTable)/ this.priceUnit();
                    percent1               += priceDiscTable.Percent1;
                    percent2               += priceDiscTable.Percent2;
                      // Begin: Alexey Voytsekhovskiy Not to lose the buffer!
                    actualDiscTable        =  priceDiscTable.data();
                    //actualDiscTable        = priceDiscTable;
                    // End: Alexey Voytsekhovskiy
                      
                  }
            }
            else
            {
                if (localQuantityAmountFrom <= qty
                    && (qty < localQuantityAmountTo || !localQuantityAmountTo))
                {
                    reselectBuffer();

                    discExist               = true;
                    discAmount             += this.calcCur2CurPriceAmount(priceDiscTable.Amount, priceDiscTable)/ this.priceUnit();
                    percent1               += priceDiscTable.Percent1;
                    percent2               += priceDiscTable.Percent2;
                      // Begin: Alexey Voytsekhovskiy Not to lose the buffer!
                    actualDiscTable        =  priceDiscTable.data();
                    //actualDiscTable        = priceDiscTable;
                    // End: Alexey Voytsekhovskiy
                  }
            }
        }
    }

 
The subroutine findPrice in findPriceAgreement method is called inside the buffer loop and must be fixed as follows.
void findPrice()
    {
        if (((discDate >= localFromDate || ! localFromDate)
            &&(discDate <= localToDate  || ! localToDate))
        && (localQuantityAmountFrom <= absQty
            &&(localQuantityAmountTo > absQty || !localQuantityAmountTo)))
        {
            if (cacheMode)
            {
                priceDiscTable = PriceDiscTable::findRecId(localRecid);
            }

            if (this.calcCur2CurPriceAmount(priceDiscTable.calcPriceAmount(absQty),  priceDiscTable) < this.calcPriceAmount(absQty) ||
                ! priceExist)
            {
                priceUnit               = priceDiscTable.priceUnit();
                price                   = this.calcCur2CurPriceAmount(priceDiscTable.price(),  priceDiscTable);

                if (salesParameters.ApplySmartRoundingAfterConversion && (priceDiscTable.Currency != currency) &&
                    relation == PriceType::PriceSales)
                {
                    price = PriceDiscSmartRounding::smartRound(price,Currency::find(currency));
                }

                markup                  = this.calcCur2CurPriceAmount(priceDiscTable.markup(),  priceDiscTable);

                pdsCalculationId        = priceDiscTable.PDSCalculationId;

                if (priceDiscTable.DisregardLeadTime)
                {
                    this.updateLeadTime();
                }
                else
                {
                    deliveryDays        = priceDiscTable.DeliveryTime;
                    calendarDays        = priceDiscTable.CalendarDays;
                }

                // <GEERU>
                inventBaileeFreeDays    = priceDiscTable.InventBaileeFreeDays_RU;
                // </GEERU>
                  // Begin: Alexey Voytsekhovskiy, Not to lose the buffer!
                actualPriceTable        = priceDiscTable.data();
                 // actualPriceTable        = priceDiscTable;
                // End: Alexey Voytsekhovskiy
                  
                  priceExist              = true;
  
                  // <GIN>
                  // Begin: Alexey Voytsekhovskiy, ThinkMax, 04Dec13, UAP_FDD017_PriceSimulation
                uapPriceDiscTableRecId  = priceDiscTable.RecId;
                // End: Alexey Voytsekhovskiy, ThinkMax, 04Dec13, UAP_FDD017_PriceSimulation
                  if (countryRegion_IN)
                {
                    // Firstly, retrieve the MRP from the trade agreement. If there is no MRP defined in
                    // the trade agreement, the MRP should be retrieved from the item master.
                    maxRetailPrice = this.calcCur2CurPriceAmount(
                        priceDiscTable.MaximumRetailPrice_IN ?
                            priceDiscTable.MaximumRetailPrice_IN :
                            InventTableModule::find(itemId, moduleType).maxRetailPrice_IN(),
                        priceDiscTable);
                }
                // </GIN>
            }
        }
    }

 
Happy pricing!

Friday, October 24, 2014

How to iterate project group members: Tables, EDT, etc

Based on S. Kuskov's suggestion and Vania Kashperuk's article, I put down this simple job that iterates Tables and Extended Data Types groups members in a given shared project.
static void tmxIterateProjectGroupMembers(Args _args)
{
    #aot
    #properties
    Str                         projectName = "tmxEDI999";
    ProjectNode                 projectNode;
    ProjectGroupNode            ddProjectGroupNode;
    ProjectGroupNode            edtProjectGroupNode;
    ProjectGroupNode            tblProjectGroupNode;
    ProjectListNode             projectListNode;
    TreeNode                    memberTreeNode;              
    TreeNode                    projectTreeNode;
    TreeNodeIterator            projectIterator;
    
    if(projectName)
    {
        // find all shared projects
        projectListNode = SysTreeNode::getSharedProject();
        // find project with a given name
        projectNode = projectListNode.AOTfindChild(projectName);
        // open it in a separate window in AOT
        projectTreeNode = projectNode.getRunNode();
        // this is the key point after which we can iterate group members
        projectNode = projectNode.loadForInspection();
        // get nested nodes for appropriate names
        ddProjectGroupNode = projectNode.AOTfindChild('DataDictionary');
        edtProjectGroupNode = ddProjectGroupNode.AOTfindChild('Extended Data Types');
        tblProjectGroupNode = ddProjectGroupNode.AOTfindChild('Tables');
        
        // tables
        projectIterator = tblProjectGroupNode.AOTiterator();
        memberTreeNode = projectIterator.next();

        while(memberTreeNode)
        {
            info(strFmt("%1 %2", memberTreeNode.AOTname(), memberTreeNode.treeNodeName()));
            memberTreeNode = projectIterator.next();
        }

        // extended data types
        projectIterator = edtProjectGroupNode.AOTiterator();
        memberTreeNode = projectIterator.next();

        while(memberTreeNode)
        {
            info(strFmt("%1 %2", memberTreeNode.AOTname(), memberTreeNode.treeNodeName()));
            memberTreeNode = projectIterator.next();
        }
    }
}
The key method is loadForInspection. Happy iterating!

Wednesday, October 8, 2014

C# code to test EDI-XML transformation

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using Microsoft.Dynamics.IntegrationFramework.Transform;
using tmxEDITransformsX12.SharedXSD;
using tmxEDITransformsX12.Transform820XSD;

namespace TransformTest
{
    class Program
    {
        static void Main(string[] args)
        {
            FileStream input = new FileStream("C:\\Test.edi", FileMode.Open);
            FileStream output = new FileStream("C:\\Output.xml", FileMode.OpenOrCreate);

            tmxEDITransformsX12.Transform820 transform = new tmxEDITransformsX12.Transform820();

            transform.Transform(input, output, "");


        }
    }
}

Wednesday, September 24, 2014

How to find in AOT all objects named like...

In fact the standard Find tool in AOT works well if you really know how to fill in all these parameters before launch it. Sometimes it is easier to use another options.

Let's say we need to find all the forms that contain 'lookup' word in the end of their names. Easy? Yes, it is! You can go directly to the SysModelElement table and work with it as with any other table: Ctrl-G to switch grid filter, 11 for Form element type, then *lookup in the name, and that's it.



This is rapid way to know the objects names but it is not possible to jump to them in AOT. Here we go with the second option. Just create a new project and filter criteria Element type name and Name for the objects. 


Simple, fast and will keep the search results for future recalls.



Happy searching in AOT!

Saturday, September 13, 2014

How to add a new relation on a table from code

If you got into troubles, like me, trying to add a new relation on a table from X++, this code will save your day. The main point here is the code 251. Read my lips: two-five-one. Because reflections work on everything when you know the code.



private void createTableRelationInAOT(TreeNode _treeNodeTableRelation, TableName _relatedTableName, FieldName _relatedFieldName)
{
#Properties    
    TreeNode            treeNodeRelations;
    TreeNode            tableRelation;
    ReferenceNode       tableRelationFields;

    if(treeNodeTableRelation && _relatedTableName && _relatedFieldName)
    {
        treeNodeRelations = _treeNodeTableRelation.AOTfindChild(#PropertyRelations);
        tableRelation = treeNodeRelations.AOTadd(relatedTableName);
        tableRelation.AOTsetProperty('Table', relatedTableName);
        //can anyone tell me how I could guess this secret code??
        tableRelationFields = tableRelation.AOTaddSubNode(251); 
        //never change the order of two these properties! it works only like follows
        tableRelationFields.AOTsetProperty(#PropertySecField, relatedFieldName);
        tableRelationFields.AOTsetProperty(#PropertyRelatedField, relatedFieldName);
    }
    _treeNodeTableRelation.AOTsave();

}


Happy deciphering!

Sasha Nazarov added:

 "251" corresponds to #NT_DBNORMALREFERENCEFIELD

in TreeNodeSysNodeType macro:
#define.NT_DBREFERENCE( 250)
#define.NT_DBNORMALREFERENCEFIELD( 251)
#define.NT_DBTHISFIXEDREFERENCEFIELD( 252) #define.NT_DBEXTERNFIXEDREFERENCEFIELD( 253)

Thursday, September 11, 2014

Problem with importing a temporary table/class/etc

I bumped into a strange problem. The admin refreshed my development environment from the TEST one; consequently, all my objects were deleted.

When I started re-importing them from an exported xpo-project, a temporary table always caused the following error:

A table, Extended Data Type, Base Enum or class called XXXXX already exists. Import of Table aborted.


The problem can be easily resolved by flushing the user cache:
Stop AX client and delete all  ax_*.auc files in "C:\Users\%USERNAME%\AppData\Local folder" 

Monday, August 25, 2014

How to fix internet speed drop via Wi-Fi connection (Cisco router)

Having read a lot of tricks and tweaks on how to fix speed drop, I found a solution in my Cisco Wi-Fi router QoS settings.


Friday, August 15, 2014

Display methods and Caching on forms

Even though the form controls using display methods are deleted from the form, they are still executed if added in cache in Form datasource Init() method.

Wednesday, June 18, 2014

How to count query loops number

Amongst our other mundane chores, counting of a query loops is not the trickiest one but, unfortunately, still clumsy developed even in AX 2012.

 Not only is it strange to have two different methods for one only and multiple data sources, but it takes long time and returns incorrect results when it comes to groupings.

 SysQuery::countTotal and SysQuery::countLoops use the private method SysQuery::countPrim, which can be redone in a way suggested here (in Russian).

private server static container сountPrim(container _queryPack)
{
    Query                   countQuery;
    QueryRun                countQueryRun;
    QueryBuildDataSource    qbds;
    QueryBuildFieldList     qbfl;
    Common                  common;
    Integer                 counter;
    Integer                 loops;
    
    Integer                 tmxGroupNumber;
    Integer                 tmxDataSourceNumber;
    ;
    countQueryRun   = new QueryRun(_queryPack);
    countQuery      = countQueryRun.query();
    tmxGroupNumber  = countQuery.groupByFieldCount(); //<-- this guarantees number of groupings

    for (tmxDataSourceNumber = 1; tmxDataSourceNumber <= countQuery.dataSourceCount(); tmxDataSourceNumber++)
    {
        qbds = countQuery.dataSourceNo(tmxDataSourceNumber);
         qbds.update(false);
        //qbds.sortClear();

        //tmxGroupNumber +=(qbds.orderMode()==orderMode::GroupBy); 
        qbfl = qbds.fields();
        qbfl.dynamic(false);
        qbfl.clearFieldList();
         qbds.addSelectionField(fieldNum(Common,RecId),SelectionField::Count);
    }
  
    countQueryRun   = new QueryRun(countQuery);

    while (countQueryRun.next())
    {
         common  = countQueryRun.getNo(1);
        counter += common.RecId;
        loops++;
    }
    //return [counter,loops];
    return [counter,(tmxGroupNumber ? loops : counter)];
 }

Thursday, May 29, 2014

How to update cross-reference in batch

As you can see in AX 2012 updating cross references is not a batch job any more. But is worth setting it up to be run automatically during the night.

There is the article on MSDN describing this subject but without a batch, and if it looks too complicated for you, as for me, I would suggest another way found on AXForum (in Russian).

Once you started cross-reference updating from the client, you can easily find this job in Batch Job menu.





As we can see it is xRefUpdateIL class used for this goal, that in turn runs UpdateAll method.




So, it is possible to create your own job or batchable class to run, but we can just change the recurrence and set up other parameters, like alerts, for the ended batch job and change then its status to Waiting.



The same is true for partial reference update, say, for certain tables and classes of a project.



Happy cross-reference updating!


Thursday, May 1, 2014

Clean off the illegal characters from file name

When it comes to use file names, say, in saving reports on the disk or sending them as an attachment, they must conform OS restrictions.

Although we cannot rely on GetInvalidFileNameChars function due to its limitation, it is easy to create your own procedure that clean off the illegal characters from the given file name.

In my example I use a regular expression and a set of characters that should be replaced with the underscore by default.


/// <summary>
/// Checks for invalid characters in the filename and changes them with underscore.
/// </summary>
/// <param name="_fileName">
/// The file name value.
/// </param>
/// <param name="_char2use">
/// The character to use instead of illegal characters. Underscore by default.
/// </param>
/// <returns>
/// valid file name
/// </returns>

static client str makeFileNameValid(str _fileName, str 1 _char2use = "_")
{
    str                 pattern = "[/:*?'<>|]";
    str                 fileName;

    fileName = System.Text.RegularExpressions.Regex::Replace(_fileName, pattern, _char2use);
    fileName = strReplace(fileName , '\\', _char2use);
    return fileName;
}

I used this article.

Tuesday, April 29, 2014

How to get rid of hyper links in SSRS report

As we remember AX 2012 allows us to jump directly to the details based on EDT or table relations. Sometimes this weight a report that supposed to be saved in Excel, for example.

It is easy to suppress hyper links in Visual Studio by changing field property in design.


Happy DAXing!

Thursday, January 23, 2014

Bug: One character in dialog field label causes wrong redrawing

Working on my previous project to change batch caption dialog field, I found a strange bug when it came to redrawing dialog window.

If we place any dialog field below a combobox with a label even of one character longer than the latter, AX fails to redraw the window entirely after modifying combobox value and changing Main instruction text.



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

    dialogEventType = dlg.addFieldValue(EnumStr(uapInterfaceEventType), eventType, '1234567890' );
    dialogAnother   = dlg.addFieldValue(identifierStr(VendName), vendName, '12345678901');
    // 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;
}

Wednesday, January 22, 2014

Initial parameter default with SysOperation (initParmDefault)

With the new SysOperation framework in AX2012 I bumped into a simple question on how to set default value for one parameter in the data contract as I did by using initParmDefault with RunBase class.

Here are two ways to do that: we can initialize any values inside of new() method in the data contract class or, alternatively, override initializeServiceParameter() in the service controller class for a particular data contract type.


The latter always overrides initial default values coming with the data contract.

I used information from this article (in German).

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!



Bug: Drag-n-Drop Creates New Element In Enums With Duplicate Values

Bug in AX 2012 R2.

When you use drag-n-drop in AOT to create a new element for a enum, it creates this element with the exactly same value.





But all enum values must be unique.



It can lead to eventual errors in run time.
Please send a bug report to Microsoft.

Saturday, January 11, 2014

How to turn off magic quotes gpc for Joomla 3

This post is devoted to Joomla CMS.

New 3.2 Joomla version requries to make off the magic quotes option. There is a good article on how to that via php.ini and .htaccess files in local folders on your web site.

Unfortunately it does not work php parameters propagated entirely for all folders and subdomains from the server settings.


 Here is the way to change it via the control panel.

Log in to your web hosting control panel.


Go to General Options\Software.


Then press light blue button "PHP CONFIGURATION" (Now I understand why it is capitalized)


Here press "Show PHP Settings" button.

And now the time to ask your evident question "Where in the world are my PHP settings?!" Take it easy, guys! You just need to change "PHP Version" parameter to any other one and then change it back to the version you work in. Do not press "Set as current" button!



Bingo! This is the turn.

Now just change "magic_quotes_gpc" option to OFF and do not forget to save the changes. That's it. You magic quotes successfully turned off.



Happy Joomling!


Friday, January 10, 2014

Windows Admin Trick: How to restart Windows In a Remote Session

Found it useful to create a batch file:

@echo off
echo Shutting down in 10 seconds. Please type "shutdown /a" to abort.
cmd.exe /K shutdown /f /t 10 /r