Pages

Wednesday, May 4, 2016

The better way to detect when multiple records are selected

There are some instances when you want to know when multiple records are marked on a form. An example would be if you wanted a button to be enabled only when more than 1 record was selected.

You can make a button disabled when more than 1 record is selected, but not the other way around.

As far as I can tell, the way the grid functions is it stores an Array object of the cursor positions of the marked records. Below are the two ways I've seen to solve this problem. Method #2 is more common, but Method #1 seems more efficient to me as you're only interacting with the Array and not traversing the datasource's table buffer.


    FormDataSource  fds = salesTable.dataSource();

    // Method 1
    // The grid stores an array of marked records, and this
    // will return only the index, which is all that is needed
    if (fds.recordsMarked().lastIndex() > 1)
        info("Multiple records selected");
    else
        info("1 or 0 records selected");
    
    // Method 2
    // This will traverse the datasources buffer
    fds.getFirst(true);    
    if (fds.getNext())
        info("Multiple records selected");
    else
        info("1 or 0 records selected");

    // Enabling/Disabling button in one line
    myButton.enabled(SalesTable_ds.recordsMarked().lastIndex() > 1);

And how to enable/disable a button in one line:

    // Enabling/Disabling button in one line
    myButton.enabled(SalesTable_ds.recordsMarked().lastIndex() > 1);

Thursday, December 3, 2015

Some notes on Dynamics AX 2012 and the registry


  • [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AOS60$01]
    • Windows service configurations
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Dynamics Server
    • AOS Server(s) configurations
  • HKEY_CURRENT_USER\Software\Microsoft\Dynamics\
    • When a user opens the client config, this registry key is created and it stores their configurations which take priority over HKLM
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Dynamics
    • These are the client config settings for the machine that are used when HKCU settings aren't present.

Tuesday, November 10, 2015

CacheDataMethod (AX2012 feature) property vs CacheAddMethod

A someone little known feature of AX 2012 is a new property called "CacheDataMethod".

Previously, when you wanted to improve performance by caching your display/edit methods, you would place a line of code like this after the super() in the datasource's init() method:

this.cacheAddMethod(tableMethodStr(CustTable, MyDisplayMethod));

In AX 2012, you can just change the "CacheDataMethod" property on the object without adding code.  I prefer this method where possible mainly because it keeps everything packaged together and I don't have to intermingle my code with base code.


Monday, November 9, 2015

The better way to pass containers between objects/forms using the Args() class, and not by converting to a string

If you need to pass a container between objects/forms using the Args class, don't convert it to a string and then back, use parmObject() and ContainerClass()!  I see many suggestions about converting it to a string, which can have much more unpredictable results and is not as versatile.

You wrap your container in the class ContainerClass() and then unwrap it at the other end.

Make your call like this:
args.parmObject(new ContainerClass(["Real container", 1234, "Not con2str container"]));
And retrieve it like this:
containerClass = element.args().parmObject() as ContainerClass;
myContainer = containerClass.value();

To test this, create a form (Form1) and overwrite the init method and put in this code:

public void init()
{
    ContainerClass      containerClass;
    container           conValue;
    
    if (!(element.args() && element.args().parmObject() && element.args().parmObject() is ContainerClass))
        throw error("@SYS22539");
    
    super();
    
    containerClass = element.args().parmObject() as ContainerClass;
    conValue = containerClass.value();
    
    info(strFmt("The container contains '%1'", con2Str(conValue)));
}

Then create a Job and put in this code:

static void JobForm1(Args _args)
{
    Args        args;
    FormRun     formRun;
    
    args = new Args();
    args.name(formStr(Form1));
    args.parmObject(new ContainerClass(['Real containers', 1234, 'Not con2str containers']));
    
    formRun = classFactory.formRunClass(args);
    formRun.init();
    formRun.run();
    formRun.wait();
}

And then run the job!


Wednesday, October 28, 2015

How to create a self-elevating PowerShell script that will run as administrator every time

Often there are various build processes or other automated tasks that run via PowerShell and need to be run as administrator.  If you forget to run it as administrator, it won't work, and you don't always know.

I came across a great blog post by Ben Armstrong that I have to share, where he's created a block of code you just prefix to the beginning of your PowerShell script that will re-launch it as administrator if it is not.  Check his post out here:

http://blogs.msdn.com/b/virtual_pc_guy/archive/2010/09/23/a-self-elevating-powershell-script.aspx

Here's a screenshot of the PowerShell code in case his blog goes down:

Tuesday, October 13, 2015

Without any customization, how to incrementally compile the CIL from the command line

With Dynamics AX 2012, you can start an incremental CIL compile from the command line (or Power Shell) without any customization.  This is useful if you have any automated processes that import XPOs frequently and you don't want to constantly build the full CIL.

Create an XML file with this data:


<?xml version="1.0" ?>
<AxaptaAutoRun  
    exitWhenDone="true"  
    version="6.2"  
    logFile="C:\AxaptaAutorun.log"> 
 <CompileIL incremental="true" />
</AxaptaAutoRun>

Then save it in a place that is accessible from the AOS service account.  I saved it as "C:\IncrementalCIL.xml" on the AOS machine.

Then run this command, subbing in for your environment:

"C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\Ax32.exe" \\MyNetworkShare\AOS.axc -startupcmd=autorun_C:\IncrementalCIL.xml

Some notes about the XML.  The version attribute must not be greater than your system's build, which can be found from calling xInfo::releaseVersion().

There are a TON more autorun features available, and you can use the following links or just dig into \Classes\SysAutoRun.

More information can be found at:





Thursday, September 10, 2015

How to export all private or shared projects with or without project definitions from a specified layer

I often have to switch development machines, and when I do, I lose all of my private or shared projects.  This is a good way to backup your projects and/or their definitions.


static void AKExportProjects(Args _args)
{
    #AotExport
    TreeNodeIterator        tni;
    ProjectNode             projectNode;
    int                     exportFlag;
    Dialog                  dialog = new Dialog();
    DialogField             folderName;
    DialogField             projectDefinitionOnly;
    DialogField             exportFromLayer;
    DialogField             projectType;
    UtilEntryLevel          layer;

    dialog.addText("This will export all projects (shared or private) that exist in a selected model.");
    projectType             = dialog.addFieldValue(enumStr(ProjectSharedPrivate), ProjectSharedPrivate::ProjPrivate);
    projectDefinitionOnly   = dialog.addField(extendedTypeStr(NoYesId), 'Project Definition Only');
    folderName              = dialog.addField(extendedTypeStr(FilePath));
    exportFromLayer         = dialog.addField(enumStr(UtilEntryLevel), 'Projects from layer');

    dialog.run();

    if (dialog.closedOk())
    {
        if (!folderName.value())
            throw error("Missing folder");

        exportFlag = #export;
        if (projectDefinitionOnly.value())
            exportFlag += #expProjectOnly;

        layer = exportFromLayer.value();
        
        switch (projectType.value())
        {
            case ProjectSharedPrivate::ProjPrivate:
                tni = SysTreeNode::getPrivateProject().AOTiterator();
                break;

            case ProjectSharedPrivate::ProjShared:
                tni = SysTreeNode::getSharedProject().AOTiterator();
                break;

        }
        
        projectNode = tni.next() as ProjectNode;
        
        while (projectNode)
        {
            if (projectNode.AOTLayer() == layer)
                projectNode.treeNodeExport(folderName.value() + '\\' + projectNode.name() + '.xpo', exportFlag);

            projectNode = tni.next() as ProjectNode;
        }
    }
    else
        warning("No action taken...");
}