Posted: Thu Feb 22, 2007 12:33 am Post subject: Controlando Axapta desde documentos externos
//**********************************************************
// Tipo : Desarrollo
// Titulo : Controlando Axapta desde documentos externos
// Codigo : TD0015
// Keywords : Programacion, Axapta 3.0
// Sub-keywords : SysStartupCmd, axlink, DDE, links, html
//
//**********************************************************
Controlando Axapta desde documentos externos
Introducción
Debe hacer ya unos 7 meses que empecé con este truco y al cabo de una semana lo dejé de lado al topar con un pequeño problema de difícil solución. Pasado todo este tiempo no he podido dedicar ni un minuto de tiempo a continuar este truco, pero ... pensándolo bien ... el “pequeño” problema es realmente muy pequeño, lo suficiente como para obviarlo y sacar a la luz de una vez por todas esta pequeña joya que tenía guardada cual Gollum con su tesoro
Objetivo
Sería genial poder abrir una página web o un informe HTML, PDF o cualquier otro tipo de presentación que permita el uso de hyperlinks y poder establecer vínculos dinámicos entre la información que se está mostrando y la aplicación de Axapta.
Es decir, que pudiéramos estar viendo una estadística de ventas por clientes y al hacer clic sobre el código de cliente en el informe ... Axapta nos abriera el formulario de clientes filtrando para mostrarnos los datos del cliente seleccionado.
Bien, pues ... no es ciencia ficción , es posible y vamos a explicar como.
Primer paso : SysStartupCmd
El primer paso será conseguir asociar un tipo de link para que Windows ejecute nuestra aplicación Axapta pasando unos parámetros donde indicará que instrucciones debe seguir al arrancar.
Para ello vamos a crear nuestro parámetro de arranque de Axapta al que vamos a llamar ‘openlink’. De esta forma sabemos que cada vez que se arranque Axapta con este parámetro significa que debemos actuar
Por tanto, creamos nuestra clase :
x++:
// TrucosAxapta.com // Clase para implementar el startupcmd=openlink class SysStartupCmdOpenLink extends SysStartupCmd
{
}
Heredamos de SysStartupCmd, por tanto ... tendremos que tocar el método construct de la clase SysStartupCmd para que cree una instancia de nuestra clase cuando sea necesario :
x++:
static SysStartupCmd construct(str startupCommand) { str s = strReplace(startupCommand,'_',' ');
int p = strScan(s,' ',1,strLen(s));
str parm;
Esta bien, ahora tenemos que cuando alguien ejecute Axapta con el parámetro “–startupcmd=openlink” ... el sistema creará nuestra clase y podremos ejecutar lo que deseemos.
Nota : Como veremos más adelante, la forma correcta será :
‘ax32.exe -startupcmd=openlink_axlink:............................’
Bien, pero ... ¿que haremos?
Definiendo un protocolo
Vamos a establecer un protocolo básico mediante el que podremos especificar que acciones debe realizar Axapta cuando pasemos los parámetros “openlink”.
Como es posible que más adelante nos animemos a definir diferentes tipos de links y parámetros varios ... lo mejor será que identifiquemos el tipo de link al comienzo de la ristra de parámetros y luego cada tipo tendrá sus pequeñas variaciones.
La forma básica de los parámetros sería una línea de texto en la que se separarán los parámetros mediante puntos y coma ‘;’. Luego la forma de cada parámetro podría ser .... parámetro = valor, por tanto tenemos algo así como :
TYPE=XXXXX;PARM1=P1;PARM2=P2;.......
En donde XXXXX será el tipo de link que se debe procesar e irá seguido de todos los parámetros necesarios para el correcto funcionamiento de dicho tipo de link.
Por ejemplo, si tenemos un tipo de link que se encarga de abrir formularios... lo lógico será que se le pase un parámetro en el que se indique el nombre del formulario que deseamos abrir :
TYPE=FormRun;FORMRUN=CustTable
Bueno, esta idea ya va teniendo forma, ahora tendremos que hacerla realidad
La clase base : ta_AxLinks
Partiendo de la idea de tener diferentes tipos de links para realizar distintas operaciones, vamos a crear una clase base de la que luego podremos heredar para cada tipo de link e implementando un par de funciones dispondremos de una amplia gama de acciones.
Nuestra classdeclaration será :
x++:
// TrucosAxapta.com // Clase que interpreta los links pasados a Axapta mediante openlink class ta_AxLinks
{ str LinkStr;
container LinkItems; // Aqui guardaremos ya separados los diferentes items de la cadena link }
En la función new recibiremos la cadena de caracteres que almacena los parámetros :
x++:
voidnew(str _LinkStr ) {
LinkStr = _LinkStr;
this.Init();
}
La almacenamos en la variable global LinkStr y llamamos a INIT :
En la función INIT convertimos la cadena de caracteres en un container de forma que cada parámetro será un elemento del container. De eso se ha encargado la función estática ParseLinkStr que viene a continuación :
x++:
// Devuelve un container con los elementos que encuentra en el linkstr diferenciados // El separador es ; ejemplo: Type=FormRun;FormRun=CustTable son 2 elementos. staticcontainer ParseLinkStr(str LinkStr ) { return str2con(LinkStr,';');
}
Ahora nuestra clase necesita un método encargado de ejecutar las acciones que ha recibido por parámetro, por tanto, crearemos un método llamado RUN :
Evidentemente no podemos ejecutar nada a la ligera sin realizar antes un par de comprobaciones ... para ello la función validatelink que devolverá true si todo es correcto y de esta forma proseguiremos llamando a la función DoLink que se encargará propiamente de ejecutar nuestro link.
x++:
// Función que validará el texto pasado para comprobar si tenemos los parametros necesarios
boolean validateLink() { returntrue;
}
x++:
// Función que ejecuta el link void DoLink() {
}
De todas formas como hemos dicho que esta solo era la clase base, estas funciones están vacías y son las clases que luego iremos creando y hereden de la clase base las que tendrán que implementar el validatelink y el DoLink.
Nos falta lo más importante ... el método construct que creará una instancia de nuestra clase en función del tipo de link que recibamos como parámetro.
if(Clave != 'TYPE') throw error('El primer elemento de la cadena del link debe especificar el tipo !. Ej: TYPE=FORMRUN;.....');
tipo = conpeek(PrimerItem,2);
tipo = StrlrTrim(tipo);
// Ahora a ver que tipo de nodo creamos switch(tipo) { case'FormRun' : returnnew ta_AxLinksFormRun(LinkStr); break; //
default :
throw error(strFmt('Tipo de link %1 desconocido para el sistema',tipo));
}
}
Si examináis el código comprobaréis dos cosas :
Primera que realizamos una llamada a otra función estática de ta_AxLinks llamada ParseLinkStrItem. Esta se encarga de recibir una cadena de tipo “clave=valor” y devolvernos un container de dos elementos, clave y valor.
x++:
// Devuelve un container con las dos partes separadas del string // clave = valor staticcontainer ParseLinkStrItem(str LinkStrItem ) { return str2con(LinkStrItem,'=');
}
Es decir, que la forma final de la clase base sería como en la imagen :
La segunda cosa es que ya tenemos un tipo de link definido, el tipo ‘FormRun’ y la clase que lo procesa es la ta_AxLinksFormRun. Esto pasa porque lógicamente, antes de explicar todo esto a nadie ... yo ya lo he probado
O sea que vamos directos a implementar el tipo de link ‘FormRun’.
El tipo de link ‘FormRun’ : ta_AxLinksFormRun
Este tipo de link se encargará de abrir un formulario que le indiquen por parámetro. Luego añadiremos soporte para añadir QueryRanges a las tablas del formulario que acabamos de abrir, por supuesto leyendo dichos QueryRanges de los parámetros.
x++:
// TrucosAxapta.com // Clase que usamos para abrir links de tipo FormRun class ta_AxLinksFormRun extends ta_AxLinks
{ }
Como podemos apreciar, heredamos de ta_AxLinks.
Ahora debemos implementar aquellos dos métodos de los que hablábamos con anterioridad, validatelink y DoLink.
if(ret) { // Miramos el segundo elemento de la cadena de links, ya que ... // se supone que debe indicarnos el formrun a ejecutar FORMRUN=xxxxx
FrItem = ta_AxLinks::ParseLinkStrItem(conpeek(LinkItems,2));
if(StrlrTrim(conpeek(FrItem,1)) != 'FormRun')
ret = checkfailed('Se esperaba que el segundo elemento de la cadena link indicara el formRun');
}
return ret;
}
La forma de cadena que esperamos es :
TYPE=FormRun;FORMRUN=xxxxxxx;........
Por tanto, el primer elemento de la cadena es ‘TYPE=FormRun’ y el segundo elemento de la cadena debe ser ‘FORMRUN=xxxxxxx’. Si no es así ... nos quejamos
Suponiendo que todo sea correcto, tal y como hemos visto cuando analizábamos la clase base, si validatelink devuelve true, es el turno de la función DoLink :
// Obtenemos el nombre del formulario, que esta en el segundo item de la cadena links // recordamos que es de la forma ... TYPE=FormRun;FormRun=XXXXX;......
iFormRun = ta_AxLinks::ParseLinkStrItem(conpeek(LinkItems,2));
FormName = StrlrTrim(conpeek(IFormRun,2));
// Lo creamos
args = new args();
args.name(formName);
formRun = classFactory.formRunClass(args);
if(! formRun) throw error(strfmt('Se ha producido un error al intentar crear el form %1',FormName));
formRun.init();
Fds = formRun.objectSet();
Query = Fds.query();
// Vamos a procesar el resto de parametros a ver si hay algún QueryRange=xxx:xxx:xxx // for(i=3; i<=conlen(LinkItems); i++) {
iQRange = ta_AxLinks::ParseLinkStrItem(conpeek(LinkItems,i));
if(StrlrTrim(conpeek(iQRange,1)) == 'QueryRange') {
this.AddQueryRange(Query,StrlrTrim(conpeek(iQRange,2)));
} // Else ... si no es QueryRange ... es desconocido, o almenos por ahora // optamos por no hacer nada.
}
formRun.run();
formRun.wait();
}
Como se puede apreciar, recogemos el parámetro del nombre de formulario y creamos dicho formulario.
Luego viene una parte interesante, es cuando recorremos el resto de parámetros que puedan existir en la cadena de texto.
Tal y como aparece comentado en el código, hemos ampliado el protocolo añadiendo un parámetro ‘QueryRange’ con la forma 'QueryRange=tabla:campo:rango’. Fijaos que en un mismo parámetro aceptamos 3 valores distintos, la tabla a filtrar, el campo y el valor del rango todos ellos separados por dos puntos ‘:’.
Es por eso que realizamos un bucle mientras existan otros parámetros de entrada y si son de tipo ‘QueryRange’ los procesamos en la función AddQueryRange.
// Como de costumbre guardamos en un container los diferentes items del string // La forma de los rangos sera : QUERYRANGE=Tabla:Campo:ValorRango
rangeItems = str2con(RangeString,':'); //
TableName = StrlrTrim(conpeek(rangeItems,1));
TableId = tablename2Id(TableName);
if(! TableId) throw error(strFmt('Imposible crear rango para tabla %1, tabla inexistente',TableName));
QBDS = Query.dataSourceTable(TableId);
if(! QBDS) throw error(StrFmt('Imposible crear rango para tabla %1, tabla encontrada en el formulario',TableName));
// Ahora el campo
FieldName = StrlrTrim(conpeek(rangeItems,2));
FieldId = fieldname2Id(TableId, FieldName);
if(! FieldId) throw error(StrFmt('Imposible crear rango para tabla %1, campo %2 no existe',TableName, FieldName));
// Como ya nos ha sucedido alguna vez, str2con no se aclara con valores alfanumericos // estilo 003 y los trata como numericos ... etc // Para evitar problemas el tercer item (el valor del rango) lo cogemos a la brava
RangeValue = substr(RangeString,
strscan(RangeString,':',strlen(RangeString),-strLen(RangeString))+1,
strlen(rangeString)); //
QBR.value(StrlrTrim(RangeValue));
QBR.status(RangeStatus::Locked);
}
En esta función no hacemos más que separar los 3 valores del parámetro en el container rangeItems (tabla, campo, rango) y luego aplicar dicho rango.
De todas formas, nos encontramos con algo a tener en cuenta. Al recoger el valor del rango, como este puede contener valores de tipo ‘003’ o ‘0200002’, la función str2con no determina con exactitud si se trata de un valor numérico o alfanumérico y al final se decanta por numérico :/
Con lo que no es lo mismo buscar el cliente 001 que el cliente 1. Por eso capturamos el tercer valor del parámetro de una forma más .... rupestre
Perfecto, pero ... ¿todo esto porque lo estábamos haciendo? ¡Ah si! Queríamos arrancar Axapta pasando por parámetro una serie de acciones a realizar y que este las ejecutara.
Pues entonces nos falta una cosa ...
El punto final en la clase SysStartCmdOpenLink
Como íbamos diciendo, solo nos queda crear el método que se ejecutará al arrancar Axapta con el parámetro ‘openlink’. Y eso es el método infoRun de nuestra clase SysStartCmdOpenLink.
// Primero quitamos de la cadena link el comienzo que pone el sistema
Comando = strdel(StartupCmd,1,strlen('openlink_axlink:'));
// Parseamos la cadena link y la ejecutamos
AxLinks = ta_AxLinks::construct(Comando);
AxLinks.Run();
}
Como ya hemos comentado, por convenio el parámetro que disparará nuestra clase realmente será ‘openlink_axlink:’
Bien, pues en esta función únicamente eliminamos la primera parte de la cadena (la que contiene ‘openlink_axlink:’) y ejecutamos el constructor de ta_AxLinks para que nos devuelva una instancia según el tipo de parámetro que esté llegando del sistema, para luego lanzar la ejecución del método Run.
Hasta aquí la primera fase
Pues hasta aquí la primera fase, pero ... y ¿como pruebo yo esto?
La prueba más visceral :
Bien, podemos abrir una instancia de emulador DOS (inicio / ejecutar / “cmd”)
Una vez en la ventana del DOS, entramos un comando como :
Evidentemente sin el salto de línea y sustituyendo la ruta correcta al ejecutable ax32.exe y el nombre de la configuración de Axapta que estemos usando:)
La forma más elegante :
Creamos un archivo de texto con la siguiente información :
Tenéis que sustituir en “hkey_classes_root\axlink\DefaultIcon” y en “hkey_classes_root\axlink\shell\openlink\command“ las rutas correctas a vuestra aplicación Axapta y por el nombre de configuración adecuado. CUIDADO con las dobles barras y sobretodo, si tenéis que introducir una ruta larga que va entre comillas dobles, debéis introducir una barra antes de las comillas.
Por ejemplo :
@=”\”C:\\Esta ruta es muy larga y con espacios\\ax32.exe\” –regconfig=........
Luego renombramos el archivo de texto a una extensión “.reg” y le damos doble clic para que lo incorpore al sistema.
Ahora si en una sesión DOS introducimos :
Code:
Start axlink:TYPE=FORMRUN;FORMRUN=CustTable
Nos arrancará Axapta y abrirá el formulario de clientes.
O mejor aún, podemos crear un archivo HTML (testaxlink.htm) con algo como :
Si lo abrimos y hacemos clic sobre “Abrir Clientes” …. ¡Tachaaaaan!
Esto me gusta, pero ... yo no quiero que abra una instancia nueva de Axapta para cada link que ejecuto.
Segunda Fase : Servidor DDE
Vamos a evitar que se lance una segunda instancia de Axapta para ejecutar un nuevo link creando un servidor DDE que atienda las peticiones del sistema de archivos de Windows. Dicho así parece algo solo apto para doctorados en física nuclear pero ahora veremos que es muy sencillo
Vamos a crear una clase que heredará de DDETopic :
x++:
// TrucosAxapta.com // Este sera nuestro servidor DDE para poder abrir links de Axapta // sin necesidad de arrancar otra instancia de Axapta cada vez class ta_DDEAxLinks extends DDETopic
{ }
Hasta aquí fácil, ¿no?
Ahora creamos un método llamado execute (bueno, realmente no lo creamos, lo rescribimos ya que es un método de la clase padre)
switch(this.getCommand(_Command,1)) { case'openlink':
// Como siempre, quitamos el prefijo del comando
comando = strdel(_Command,1,strlen('openlink axlink:'));
// Ahora lanzamos nuestra clase de links
AxLinks = ta_AxLinks::construct(comando);
AxLinks.Run();
break;
}
this.status( DDEstatus::OK);
}
Aquí simplemente obtenemos el primer fragmento de texto entre espacios del comando DDE que nos llega y si es ‘openlink’ ¡es el nuestro!
Eliminamos la primera parte (que en el caso DDE será ‘openlink axlink:’) y lanzamos nuestra ya famosa clase Axlinks para que procese el link.
En este fragmento se usa una función para obtener el primer fragmento de texto entre espacios, es esta :
x++:
str getCommand(str line, int idx) { int no = 1;
int p1 = 1;
int p2;
Estas líneas podemos añadirlas al final del classdeclaration.
Ahora creamos un nuevo método (seguimos en la clase info) :
x++:
// TrucosAxapta.com // Arrancamos el servidor DDE para dar servicio a peticiones de links void ArrancaServidorDDE_axLinks() {
ddeClient _ddeClient;
boolean primerintento = true;
void IniciaDDE() {
_ddeServer = new DdeServer("AxLinks"); //
_ddeSystem = new ta_DDEAxLinks(_ddeServer,'System');// }
;
try { // Hacemos una peticion DDE por si ya está en marcha
_ddeClient = new DDEClient("AxLinks","System");
} catch(Exception::Internal) { if(primerIntento) { // Si llega aquí es que no ha encontrado el servidor DDE, por tanto intentaremos arrancarlo
primerintento = false;
IniciaDDE(); // // A partir de ahora, si vuelve a petar es que algo no ha ido bien retry;
} }
_ddeClient = null;
}
Aquí es importante señalar que lo primero que hacemos es mirar si podemos conectarnos vía DDE con un servidor DDE que sea “AxLinks/System” (o sea, el nuestro) ya que es posible que ya esté en marcha en alguna otra instancia de Axapta y en ese caso no podemos crear otro servidor, daría error.
Es decir, si intentamos conectarnos y falla, tenemos vía libre para crear nuestro servidor DDE
Ahora solo nos queda añadir la llamada a este método cuando inicie Axapta, es decir, vamos a tocar el método startuppost (también de la clase info) :
x++:
/*
No SYS code must exist in this method
*/ void startupPost() {
this.ArrancaServidorDDE_axLinks();
}
Bien, ahora la próxima vez que arranquemos Axapta debería arrancar nuestro servidor DDE. Solo nos queda indicarle al sistema de archivos de Windows que nuestro Axapta dispone de interacción DDE.
Volvemos a editar el fichero “.reg” que habíamos creado antes y añadimos estas líneas al final :
Guardamos el archivo y le damos doble clic de nuevo (contestamos si a la pregunta).
Si ahora reiniciamos la sesión de Axapta y volvemos a abrir el archivo HTML que hemos creado antes, al darle clic sobre “Abrir Clientes” debería hacerlo sin abrir otra instancia de Axapta.
Es más, si está todo correcto, podéis probar un link como ...
Con el, Axapta os debería mostrar los clientes que empiezan por la letra ‘M’.
Y ¿cual es ese pequeño problema del que hablábamos al principio?
Pues que si, ahora que hemos ajustado el sistema para que funcione a través de DDE, si pulsamos un link cuando Axapta no está en ejecución … Windows ejecuta Axapta como hacíamos al principio y le intenta enviar los comandos DDE. Como nuestro servidor DDE no arranca hasta que hacemos login en Axapta … el sistema da un error como si no pudiera abrir el link, pero una vez aceptamos el mensaje de error y hacemos login … todo funciona ok
Supongo que ya le veis bastantes aplicaciones posibles .... Informes estadísticos interactivos, manuales de circuitos lógicos del programa que te llevan a las pantallas que debes parametrizar ... etc.
Saludos,
Mkz.
NOTA: He añadido el proyecto con las clases de este truco en la zona de downloads. Este es el link directo.
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
Axapta y Dynamics Ax son marcas registradas de Microsoft corporation. Todos los logos y marcas son propiedad de sus respectivos propietarios. Excepto trucosAx.com que este si que es mio :-). (c) 2005 by Manel Querol (Mkz) TrucosAx.com no pertenece ni está asociada a Microsoft corporation. Los fragmentos de código y proyectos importables que aquí se muestren están realizados sobre bancos de pruebas. No nos hacemos responsables de cualquier daño o pérdida de datos que se pudiera originar del hecho de instalar alguno de estos ejemplos en un sistema productivo. Es responsabilidad del usuario ser consciente del impacto que puede ocasionar en sus aplicaciones el uso del código que de aquí extraiga.