x++

Microsoft Dynamics AX, ¿Por dónde empiezo?

Introducción

La documentación existente en la red sobre desarrollo en Microsoft Dynamics AX es abundante, aunque a menudo mal estructurada, lo que puede ser un problema al principio, si alguien decide aprender a programar AX por su cuenta.

Como introducción se puede consultar el artículo correspondiente de Wikipedia, donde se encuentra una breve historia del producto (antes llamado Axapta), un resumen de los módulos funcionales mas importantes, versiones, arquitectura, y una introducción a su IDE de desarrollo: MorphX y su lenguaje de programación: X++

 

Libros

El libro Morph IT es probablemente el primer libro sobre desarrollo en AX, escrito bajo la version AX 3.0 SP4 prácticamente todo el contenido sigue siendo válido en AX 4 y 2009, salvo pequeños detalles fácilmente identificables. Es para mi opinión el mejor para empezar, y es gratis.

La serie "Inside Microsoft Dynamics AX" está escrita directamente por los desarrolladores de AX, tiene un nivel mas alto que el primero y es un libro ideal para consolidar los conocimientos de cualquier desarrollador AX, muy recomendables. El primero se puede descargar gratuítamente en PDF, el segundo sólo existe en versión papel y es prácticamente imposible encontrarlo en librerías españolas.

 

Recursos Web

 

comunidades, blogs, foros, ...

 

PartnerSource, CustomerSource y certificación

El acceso a PartnerSource o CustomerSource da acceso a la descarga de materiales de los cursos oficiales de certificación de Microsoft que es, probablemente, la mejor documentación disponible para el aprendizaje de AX, así como el acceso a toda la documentación que Microsoft publica sobre el producto (WhitePapers, Manuales, herramientas, etc, ...).

El acceso a estos portales depende del contrato de licencia del que se disponga con Microsoft tanto si se es Cliente, como Partner.

HOWTO: Crear controles y eventos dinámicamente en formularios AX

En este HOWTO me gustaría explicar el proceso completo para crear dinámicamente controles en un formulario existente (aunque también podría no existir, pero ya sería complicar demasiado) y además manejar los eventos generados por ese control tanto dentro como fuera del formulario en sí.

Lo haremos paso a paso, de lo mas facil a lo mas complicado:

Empezaremos con un formulario nuevo de AX totalmente estándar sin ninguna propiedad modificada:

Dynamic Runtime Forms | 1

El primer paso será generar controles en este formulario de forma dinámica, es decir, mediante código en tiempo de ejecución. Para ello modificaremos el método estandar run() del formulario de la siguiente manera:

public void run()
{
    FormButtonControl   fButtonCtr;
    ;
   
    super();
   
    element.lock();

    fButtonCtr = element.design().addControl(FormControlType::Button, "buttonName");
    fButtonCtr.text("Ejemplo");

    element.unLock();
}

Con lo que habremos añadido al diseño del formulario, un boton generado en tiempo de ejecución:

Dynamic Runtime Forms | 2

Añadimos otro control al formulario y establecemos alguna propiedad a los controles. Llevo todo este código a un nuevo método del formulario para mantener cierto orden:

public void run()
{
    super();

    element.drawControls();
}

private void drawControls()
{
    FormButtonControl   fButtonCtr;
    FormStringControl   fStringCtr;
    ;

    element.lock();

    // Control de texto (StringEdit), en negrita, ancho total
    fStringCtr = element.design().addControl(FormControlType::String, 'stringEditTexto');
    fStringCtr.bold(10);
    fStringCtr.widthMode(FormWidth::ColumnWidth);
    fStringCtr.text("Ejemplo");

    // Botón, con imagen incrustada y texto, ancho total
    fButtonCtr = element.design().addControl(FormControlType::Button, 'buttonUp');
    fButtonCtr.widthMode(FormWidth::ColumnWidth);
    fButtonCtr.buttonDisplay(FormButtonDisplay::TextAndImageLeft);
    fButtonCtr.normalResource(1067);
    fButtonCtr.text("Subir");

    // Botón, con imagen incrustada y texto, ancho total
    fButtonCtr = element.design().addControl(FormControlType::Button, 'buttonDown');
    fButtonCtr.widthMode(FormWidth::ColumnWidth);
    fButtonCtr.buttonDisplay(FormButtonDisplay::TextAndImageLeft);
    fButtonCtr.normalResource(1068);
    fButtonCtr.text("Bajar");

    element.unLock();
}

Esto es otra cosa!:

Dynamic Runtime Forms | 3

Bien, hasta aquí la parte fácil. Crear controles en runtime es bastante sencillo pero ahora, ¿qué hago con ellos?. Lo primero, tendremos que indicar al formulario donde puede encontrar el código manejador de eventos de los nuevos controles, primero la maner mas sencilla:

Lo primero que hay que hacer en cualquier caso es indicar a AX que tenemos métodos controladores de eventos propios, es decir, que estamos sobrecargando sus propios métodos controladores, igual que hacemos cuando "anulamos" métodos en tiempo de diseño (la traducción no es muy acertada):

public void run()
{
    // Sobrecargo los métodos de control
    element.controlMethodOverload(true);
    // ¿Dónde están los métodos de control?: Aquí mismo (this)
    element.controlMethodOverloadObject(this);

    super();

    element.drawControls();
}

Con el método controlMethodOverload indicamos que tenemos algún método sobrecargado, es decir, que busque primero nuestros métodos antes de ejecutar los suyos propios. El método controlMethodOverloadObject indica a AX dónde, en qué objeto, puede encontrar los métodos sobrecargados.

Hay que destacar dos puntos importantes:

  • El método se debe declarar siempre como public void
  • El nombre del método siempre ha de ser nombreObjeto_evento()

public void buttonUp_clicked()
{
    ;

    info(element.design().controlName('stringEditTexto').valueStr());
}

Funciona! Pero hay un problema. ¿Qué sentido tiene crear controles en tiempo de ejecución de una manera dinámica -por ejemplo, basandonos en opciones configurables en una tabla- si el código asociado a ellos debe permanecer estáticamente en el formulario? No tiene ningún sentido.

Es por esto que se hace necesaria la segunda opción, llevar tódo el código fuera del formulario, por ejemplo, a una clase, como es el caso. El formulario pués se libera del código y queda de una manera muy sencilla (e independiente totalmente de los controles añadidos en runtime):

public class FormRun extends ObjectRun
{
    // Clase manejadora de eventos
    JAEBaseForm_Mngr    baseFormMngr;
}

public void init()
{
    super();

    // Construyo una instancia de la clase manejadora asociada a
    // la instancia actual de este formulario
    baseFormMngr = JAEBaseForm_Mngr::construct(this);
}

public void run()
{
    // Sobrecargo los métödos de control
    element.controlMethodOverload(true);

    // ¿Dónde están los métödos de control?: Aquí mismo (this)
    element.controlMethodOverloadObject(baseFormMngr);

    super();

    // Crear los controles
    baseFormMngr.drawControls();
}

Y es la clase la que contiene todo el codigo dinámico, de manera que es posible reutilizarla en diferentes formularios:

class JAEBaseForm_Mngr
{
    FormRun     element;
}

private void new(FormRun _formRun)
{
    element = _formRun;
}

public static JAEBaseForm_Mngr construct(FormRun _element)
{
    return new JAEBaseForm_Mngr(_element);
}

public void drawControls()
{
    FormButtonControl   fButtonCtr;
    FormStringControl   fStringCtr;
    ;

    element.lock();

    // Control de texto (StringEdit), en negrita, ancho total
    fStringCtr = element.design().addControl(FormControlType::String, 'stringEditTexto');
    fStringCtr.bold(10);
    fStringCtr.widthMode(FormWidth::ColumnWidth);
    fStringCtr.text("Ejemplo");

    // Botón, con imagen incrustada y texto, ancho total
    fButtonCtr = element.design().addControl(FormControlType::Button, 'buttonUp');
    fButtonCtr.widthMode(FormWidth::ColumnWidth);
    fButtonCtr.buttonDisplay(FormButtonDisplay::TextAndImageLeft);
    fButtonCtr.normalResource(1067);
    fButtonCtr.text("Subir");

    // Botón, con imagen incrustada y texto, ancho total
    fButtonCtr = element.design().addControl(FormControlType::Button, 'buttonDown');
    fButtonCtr.widthMode(FormWidth::ColumnWidth);
    fButtonCtr.buttonDisplay(FormButtonDisplay::TextAndImageLeft);
    fButtonCtr.normalResource(1068);
    fButtonCtr.text("Bajar");

    element.unLock();
}

public void buttonUp_clicked()
{
    FormStringControl  fStringCtr = element.design().controlName('stringEditTexto');
    ;

    fStringCtr.text("Arriba!");
}

public void buttonDown_clicked()
{
    FormStringControl  fStringCtr = element.design().controlName('stringEditTexto');
    ;

    fStringCtr.text("Abajo!");
}

De esta manera conseguimos el objetivo. El objeto Form es estático y contendrá su propia funcionalidad que no cambiará en runtime. Sin embargo ejecuta métodos de la clase controladora que se encarga de toda la parte dinámica, tanto generar los controles, como manejar sus eventos.

Las posiblidades son enormes. Desde generar pequeños formularios a modo de diálogos, a construir formularios dinámicos basados en datos de tablas en la base de datos, igual que el estándar hace con el Configurador de Productos. De hecho las clases que generan el formulario del modelo de producto (las que empiezan por PBA*) son un buen banco de ejemplos sobre este tema.

Adjunto un proyecto con los dos objetos por si pudiera ser de utilidad.

Descargar objetos

HOWTO: Imprimir PDF desde AX mediante código

Habitualmente se presenta la necesidad de imprimir documentos desde Dynamics AX en formato PDF. Esta es una funcionalidad, presente en el estándar, se puede replicar por código según las necesidades.

El siguiente Job (basado en otro original de Giridhar Raj's Blog) indica como hacerlo para imprimir una factura pero es fácilmente extensible a otro tipo de documentos:


Job | Imprimir a PDF

Nuevo editor X++ en acción

Ya hace tiempo que vimos en el blog de Vincent's algunos de las nuevas funcionalidades del editor de X++ para la próxima version de Dynamics AX, por ejemplo, algunos detalles del auto-completar del código.

Hoy acaba de publicar un video con mas novedades, todas ellas interesantes, y lo mas importante es que se pueden ver ya funcionando. Cosas como la implementación (por fin) de la documentación XML integrada con el auto-completar (igual que en Visual Studio):

AX6 | X++ Editor

la edición multi-linea o la selección de texto en columnas:

AX6 | X++ Editor

dejan un muy buen sabor de boca.

Una pena que todavía quede bastante tiempo para poder disfrutar de estas novedades, y las que están por llegar.

HOWTO: Usar y crear Mapas (Maps) en Dynamics AX

Los mapas (Maps) son elementos particulares en Dynamics AX a los que vale la pena dedicar tiempo y aprender a utilizarlos, ya que resultan indispensables para ahorrar tiempo en modificaciones y desarrollos nuevos.

Para ver la utilidad de los Mapas pensemos un caso habitual: A una instalación estándar de AX se le añaden una serie de campos nuevos referentes a artículos (medidas, pesos, redondeos, precios, tallas, ...) particulares de esta instalación, con los cuales se tiene que trabajar en nuevos desarrollos para calculo de precios, tarifas, cantidades, ...

Lo tradicional con la mayoría de tecnologías sería añadir esos campos a las tablas necesarias (utilizando EDT's para mantener una coherencia de tipos), que podrían ser líneas de pedidos, facturas, compras, inventario, proyectos, producción ... (en resumen, muchas tablas), escribir el código necesario para la lógica requerida e implementarla en cada tabla, o utilizando clases, o ambas cosas.

Dynamics AX nos ofrece un objeto que encapsula esos campos, con su coherencia de tipos junto a la lógica que los maneja, de manera que se pueda aplicar toda esa información a las tablas necesarias de manera fácil (muy facil).

Lo vemos con un ejemplo, para el cual utilizo un mapa utilizado en muchísimas partes de la aplicación estándar y que encapsula la lógica y los campos del manejo de direcciones en AX, el mapa AddressMap.

Lo primero que haremos será crear una tabla nueva (se puede usar una existente) y buscar en el AOT el mapa estándar AddressMap (Data Dictionary > Maps > AddressMap). Desde el nodo del mapa en el AOT se pueden seleccionar los Fields necesarios y arrastrarlos a la tabla para crear los  campos.

jaestevan.com | Mapas 1

Bien, ya podemos guardar la tabla. El siguiente paso es "mapear" los campos de la nueva tabla, con el mapa, demanera que le digamos qué campo del mapa es qué campo en la tabla (el nombre no tiene por qué ser el mismo, aunque es recomenable para facilitar su comprensión).

Para ello vamos al nodo AddressMap en el AOT y añadimos un nuevo elemento a su sub-nodo Mappings. Al hacerlo nos aparecen todos los campos del mapa, para asignarlos a los campos necesarios de la tabla. Si algún campo del mapa no existe en la tabla puede dejarse sin asignar.

jaestevan.com | Mapas 2

Guardamos el mapa y con ésto ya tenemos los campos de nuestra tabla asignados al mapa estándar, vamos a probar este enlace para aplicar la lógica de direcciones a nuestra tabla, para lo cual anulamos el método modifiedField de la misma y añadimos una nueva línea:

jaestevan.com | Maps 3

Y con esto tenemos la lógica de direcciones implementada en nuestra tabla, de una manera totalmente optimizada ante futuros cambios del estándar, coherente con los tipos y las relaciones de los campos, evitando duplicar código, ... Se puede probar insertando un nuevo registro y modificando el código postal, por ejemplo.

Comentarios serán bien recibidos, los mapas es una cosa que cuanto mas se utiliza, más util parece.

Valor devuelto, no funciona en sub-modelos heredados

Al configurar una variable de modelo de producto, existen dos opciones para configurar como se comportará esta variable en caso de utilizar modelos heredados (un modelo de producto ejecuta otro modelo de producto):

Product Builder | Variables

La opción "Valor devuelto" no funciona en AX 3 y 4 (el problema esta corregido en la 2009) debido a un fallo en la programación de las clases estándar del configurador de producto.

Para solucionarlo editar el método de clase \Classes\PBATreeNodeCompile\PBAVarMethods() en su línea #101 y modificar la línea:

[snippet|type=xpp|csid=20|tagstyle=block|lineNumbering=|numberStart=]

por:

[snippet|type=xpp|csid=21|tagstyle=block|lineNumbering=|numberStart=]

Después de este cambio será necesario compilar todos los modelos de producto para volver a generar sus clases corregidas.

Error en modelo de producto con opciones de gráfico AX4

Si AX 4 está configurado en un idioma que no es inglés (yo lo he probado en castellano pero entiendo que funcionará de la misma manera en otros idiomas), al ejecutar un modelo de producto al que se hayan configurado opciones de gráfico, AX mostrará un error de ejecución. Es un error de traducción del estándar que ya está corregido en la versión 2009.

Para solucionarlo, editar el método de clase \Classes\PBAFrontEndControlWin\run(), en su línea #8, y modificar la línea:

[snippet|type=xpp|csid=18|tagstyle=block|lineNumbering=|numberStart=]

por:

[snippet|type=xpp|csid=19|tagstyle=block|lineNumbering=|numberStart=]

Después de este cambio será necesario compilar todos los modelos de producto para volver a generar sus clases.

HOWTO: AX - Distribuir ficheros con SysFileDeployment

A veces es util poder distribuir a los clientes de AX ficheros auxiliares que van a necesitar para la ejecución del programa, por ejemplo, controles ocx, librerias dll, iconos, imágenes, etc.

Gracias a André Arnaud, que amablemente respondió a una duda mía en los foros de la Microsoft Dynamics Community he descubierto el framework de AX SysFileDeployment que viene perfecto para esta tarea.

La idea de este framework es heredar clases para indicar a AX qué ficheros deben copiarse a la parte cliente cada vez que se realiza una nueva instalación de AX. Este framework tiene bastantes limitaciones pero la ventaja es que en su código se gestiona bastante bien el tema de permisos para copiar ficheros situados en el servidor hacia el cliente.

Comparto las pruebas que yo he realizado por si a alguien le pudieran servir. En primer lugar, hay que generar una nueva clase heredada de SysFileDeploymentFile indicando a AX qué fichero hay que copiar, dónde esta y hacia donde copiarlo. Esta es la estructura básica de la clase heredada, que esta bien autodocumentada por si se necesita mas personalización:

[snippet|type=xpp|csid=13|tagstyle=block|lineNumbering=|numberStart=]

Una de las limitaciones de este framework es que sólo se ejecuta la primera vez que se ejecuta el cliente en un equipo determinado. Por tanto para distribuir nuevos ficheros o para hacer pruebas, como es mi caso, habría que modificar el método applBuildNo() de la clase ApplicationVersion, para forzar al sistema a actualizar la versión. Haciéndolo de este modo, AX detectará que tenemos ficheros pendientes de actualizar en el servidor y nos mostrará el siguiente diálogo:

SysFileDeploymentFile

Como no me apetecía andar modificando la build de mi entorno de pruebas, he hecho un pequeño Job que simula el código que se ejecutaría automáticamente de manera normal:

[snippet|type=xpp|csid=12|tagstyle=block|lineNumbering=|numberStart=]

Otra de las desventajas es que parece que esta clase no funciona demasiado bien en AX 4. Yo las pruebas que acabo de hacer en 2009 SP1 sí han funcionado, salvo un pequeño problema con las rutas de origen/destino, supongo que por ejecutar todo el entorno en la misma máquina (cliente, aplicación, AOS, ..).

A partir de aquí que cada uno investigue lo que necesite.

X++ y MSIL

Publicaba en este blog el mes pasado que en la próxima versión de AX, el desarrollo de Enterprise Portal pasa a realizarse exclusivamente sobre Visual Studio. En la actual versión de AX, la 2009, ya podemos usar Visual Studio para desarrollar reports integrados en SQL Server Reporting Services, y desde varias versiones atrás AX publica cubos OLAP sobre servidores SQL Server Analysis Services. Lo que viene a confirmar la intención de integrar AX en el resto de sus productos de area empresarial, así como consolidar tecnologías concretas para trabajos concretos. Es inevitable mirar atrás y acordarse del "Proyect Green"

En otro paso mas en el acercamiento de la tecnología AX hacia .NET, hace unas semanas se publicó un video en Channel 9  mostrando el trabajo del equipo de desarrollo de AX orientado a la generación de código intermedio MSIL (Microsoft Intermediate Language). Si bien anuncian que este trabajo no se verá implementado en la próxima versión de AX, de llegar a completarse sería un avance importante en cuanto al rendimiento y a las posibilidades de integración de futuras versiones del producto.

Se acaba X++ en Enterprise Portal

Microsoft anuncia retirar en la siguiente versión de Axapta el interfaz X++ para el desarrollo de Enterprise Portal, sustituido en la última version AX 2009 por el interfaz ASP.NET.

Se puede consultar el anuncio en Partner Source y Customer Source

Aprovecho la entrada para enlazar un documento interesante sobre el mismo tema publicado recientemente: White Paper: Overview of Enterprise Portal