HOWTO: Eventos en AX 2012 para minimizar conflictos

Como se puede adivinar de algunos de mis anteriores post,  soy un fiel defensor de uno de los grandes olvidados de AX 2012: Los eventos

En mi afán de evangelizar a favor de uso, he dado con un ejemplo muy evidente de sus ventajas. A estas alturas supongo que casi todo el mundo conoce la posibilidad de añadir nuevas opciones al menú Add-Ins del entorno de desarrollo de AX 2012 y anteriores. Es realmente fácil, sólo hay que crear una nueva clase con un método main al uso, un menú ítem que apunte a esa clase, y añadir el menú ítem al menú estándar SysContextMenu. Con esto conseguimos que nuestro menú ítem se muestre al hacer click-derecho sobre los elementos del AOT, por ejemplo (Startup Project es el nuevo add-in):

SysContextMenu Add-In

Sin embargo, para hacerlo realmente bien es necesario alguna modificación más. En mi ejemplo, el nuevo add-in sirve para convertir el proyecto seleccionado en el proyecto de inicio para el usuario actual. Es simple, pero nos sirve como ejemplo de un add-in que solo debe mostrarse cuando hacemos click derecho en un proyecto, y no en cualquier otro tipo de elemento del AOT. Para eso, como muestra la documentación, debemos modificar el método verifyItem de la clase SysContextMenu, que es un infierno de if-else in switches para determinar si el elemento debe mostrarse o no, dependiendo de la clase que lo llama, el tipo de elemento y un larguísimo y desastroso etcétera.

Pero nosotros somos programadores elegantes de forma que, para no contribuir a empeorar ese desastre, en vez de modificar el método vamos a añadir un manejador de eventos (Event Handler) haciendo click-derecho en el método.

Event Handler

Ajustamos las propiedades del nuevo manejador de eventos para que apunten a una nueva clase que hemos creado para nuestro add-in:

Event Handler - Propiedades

El código que maneja el evento, más o menos el mismo código que hubiéramos incluido en el método original, es el siguiente:

/// <summary>
/// Event-Handler that will decide if the menuitem is shown or not.
/// </summary>
/// <param name="_args">
/// IdentifierName      menuItemName
/// MenuItemType        menuItemType
/// </param>
/// <remarks>
/// Is important not to break here the behaviour of the original method
/// </remarks>
public static void verifyItem_EventHandler(XppPrePostArgs _args)
{
    JAEESysContextmenu_DefaultProject   defaultProjectClass;
    SysContextMenu                      contextMenu     = _args.getThis();
   
    // Argumentos del método original
    IdentifierName                      menuItemName    = _args.getArgNum(1);
    MenuItemType                        menuItemType    = _args.getArgNum(2);

    if (menuItemType == MenuItemType::Action &&
        menuItemName == menuitemActionStr(JAEESysContextmenu_DefaultProject))
    {
        // No permitimos seleccionar varios proyectos
        if (contextMenu.selectionCount() == 1)
        {
            defaultProjectClass = JAEESysContextmenu_DefaultProject::construct(contextMenu);
            // Modifico el valor devuelto por el método original
            _args.setReturnValue(defaultProjectClass.isProjectMenu() ? 1 : 0);
        }
        else
        {
            _args.setReturnValue(0);
        }
    }
}

De esta forma nuestro manejador de eventos se ejecuta después de que se haya ejecutado el verifyItem original, permitiéndonos modificar el valor devuelto pero, como se puede ver en la imagen anterior, NO se ha modificado la clase original que sigue en la capa sys y el modelo Foundation y, por tanto, no va a afectar a futuras actualizaciones del producto. Hemos modificado la funcionalidad de la clase estándar sin causar ningún impacto sobre ella.

El proyecto que contiene el add-in totalmente funcional se puede descargar del enlace al final de este post, lo que hace lo podríamos resumir en estos métodos:

El método main se llama al hacer click sobre el menú ítem, es el punto de inicio del add-in. Lo que hace es verificar que efectivamente se ha llamado desde un elemento de tipo Proyecto y de ser así actualiza el usuario asignando este proyecto como proyecto de inicio en las opciones:

static void main(Args _args)
{
    JAEESysContextmenu_DefaultProject defaultProjectClass;

    if (!_args)
        throw error(Error::wrongUseOfFunction(funcname()));

    if (SysContextMenu::startedFrom(_args))
    {
        defaultProjectClass = JAEESysContextmenu_DefaultProject::construct(_args.parmObject());

        // Sólo se permite lla llamada a esta función desde Proyectos
        if (defaultProjectClass.isProjectMenu())
            defaultProjectClass.updateStartupProject();
        else
            throw error(Error::wrongUseOfFunction(funcname()));
    }
}

Para comprobar si el elemento que ha llamado a esta clase e sun proyecto utilizo mi función isProjectMenu. Esta lógica se puede obtener mirando los métodos existentes de la clase estándar SysContextMenu:

private boolean isProjectMenu()
{
    TreeNode  firstNode = sysContextMenu.first();

    if (firstNode.handle() == classnum(ProjectNode))
        return true;

    return false;
}

La actualización final para asignar el proyecto de inicio es muy sencilla:

private void updateStartupProject()
{
    UserInfo    userInfo;

    ttsBegin;

    select firstOnly forUpdate userInfo
        index hint Id
        where userInfo.id == curUserId();

    if (userInfo.startupProject != sysContextMenu.first().treeNodeName())
    {
        userInfo.startupProject = sysContextMenu.first().treeNodeName();
        userInfo.update();
    }

    ttsCommit;

    // TODO Create label
    info(strFmt("%1 is now your starting project.", sysContextMenu.first().treeNodeName()));
}

El uso de eventos no se ha generalizado durante el ciclo de vida de AX 2012 (probablemente ya nunca lo haga, en esta versión) pero será una parte importante del stack de desarrollo de la próxima versión, así que conviene acostumbrarse a ellos con tiempo, y de paso facilitar el trabajo de mantenimiento de las instalaciones actuales 🙂

Descarga