VISUAL PROLOG

Entorno de desarrollo


 

1 Secciones de un programa en Visual Prolog

1.1 Directivas de compilación

1.2 Sección de constantes

1.3 Sección de dominios

1.3.1 Dominios de Objetos Compuestos

1.3.2 Dominios de Objetos Compuestos Functorless

1.3.3 Sinónimos de Dominios Estándar

1.3.4 Dominios tipo Lista

1.3.5 Dominios tipo Predicado

1.4 Sección de la base de datos

1.4.1 Predicados definidos en Visual Prolog para manejar la base de datos

1.5 Sección de definición de predicados

1.5.1 Tipos de predicados

1.5.2 Ejemplos de cómo conseguir la coherencia entre reglas y predicados en el aspecto determinista

1.6 Sección de cláusulas

1.7 Sección de meta u objetivo

1.8 Ejemplo

2 El entorno de desarrollo de Visual Prolog

2.1 Aplicaciones orientadas a eventos

2.2 Aplicaciones orientadas a eventos en Visual Prolog

2.2.1 Tipos de ventanas en Visual Prolog

2.2.2 El diálogo Experto del entorno de desarrollo VPI

2.2.3 El primer programa VPI


 

Secciones de un programa en Visual Prolog

Un programa en Visual Prolog está compuesto de varias secciones que se describen a continuación:

[ÍNDICE]

Directivas de compilación

Nos permiten controlar las características del compilador. Estas directivas pueden ser cambiadas a través del menú, mediante opciones de línea de comando y en el propio código fuente. En caso de activar una misma directiva de forma distinta en el entorno y en el código fuente, prevalece la activación realizada en el código fuente. Ver Tabla 1

Directiva

Significado

bgidriver

Esta directiva se utiliza de un modo similar a este ejemplo: bgidriver "_CGA_driver_far", para establecer el tipo de controlador de gráficos que se debe linkar con un programa MS-DOS de gráficos BGI.

bgifont

Esta directiva se utiliza de un modo similar a este ejemplo: bgifont "_gothic_font_far", para establecer el tipo de fuente de texto que se debe utilizar con el programa a la hora de escribir un programa para MS-DOS basado en gráficos BGI.

check_determ

Esta directiva activa la detección de cláusulas no deterministas. Si especificamos esta directiva, Visual Prolog dará un warning cada vez que se detecte un predicado de tipo no determinista. Hay dos tipos de cláusulas no deterministas:

  • Aquellas que no poseen corte y hay una o más cláusulas que pueden casar con los mismos argumentos de entrada para un patrón de flujo.
  • Aquellas que llaman a otras no deterministas y el predicado llamado no está seguido de un corte.

code

Especifica el tamaño del array interno de código. Por defecto son 2000 párrafos (unidades de 16 bits), para la versión VP de 16 bits. En otro caso, son 10000. Por ejemplo si escribimos code=1024 tenemos un array de código de 16 * 1024 bytes=16Kbytes de tamaño.

config

Permite definir el aspecto de los programas en modo texto en MS-DOS antiguo.

diagnostics

Permite observar los un conjunto de parámetros de un programa Prolog.

error

Produce mensajes de error en tiempo de compilación. Cuando se encuentra un error de este tipo al llegar a la línea de la directiva, se visualiza en pantalla el mensaje escrito al lado de #error. Es muy útil para encontrar inconsistencias debidas al programador y violación de restricciones impuestas.

errorlevel

Permite establecer el grado de detalle con que VP informa de los errores al programador. Puede ser 0, 1 ó 2.

heap

Sirve para especificar la cantidad de memoria que se reservará para un ejecutable TSR en MS-DOS.

gstacksize

Se debe escribir en la sección MAIN para que sea tenida en cuenta y permite establecer el número de bytes de la pila global (cantidad de memoria virtual que el sistema operativo debe reservar), mientras el programa está cargado.

nobreak

En ausencia de esta directiva cada vez que se realice una llamada a un predicado, se examinará el teclado para ver si se ha pulsado ctrl-break, por lo que se ralentiza la ejecución de la aplicación y, además, ocupa un pequeño espacio de programa. El uso de la directiva evita que se genere este código examinador. Cuando se utiliza esta directiva, la única forma de salir de un bucle infinito es mediante un proceso de reset.

nowarnings

Suprime los avisos generados por la existencia de una variable una sola vez en una cláusula. Estas variables pueden ser sustituidas por variables anónimas, luego, en principio, no deben aparecer en un programa bien escrito.

printermenu

Permite la generación de código en programas MS-DOS que facilita la impresión de pantallas en modo texto, mediante la pulsación del conjunto Alt-p

project

Se usa cuando utilizamos una metodología de diseño e implementación modular. Permite especificar a qué proyecto pertenece un módulo determinado.

Tabla 1

[ÍNDICE]

Sección de constantes

En la sección de constantes podemos declarar constantes simbólicas que pueden usarse en el cuerpo del programa. La utilidad de las constantes en Visual Prolog es similar a la que estos elementos tienen en otros lenguajes de programación, normalmente, sirven para facilitar el uso de diversas cantidades, expresiones y símbolos.

La definición de la sección de constantes se encabeza con la palabra reservada CONSTANTS, y cada línea de definición es de la forma <Nombre Constante> = <Definición de Macro>.

Por ejemplo, el siguiente fragmento de programa visualiza en pantalla el contenido:

12

A=1, B=2

1 Solution

CONSTANTS

numero = 1

expresion = 1+1

GOAL

A=numero, B=expresion, write(A), write(B), nl.

Las restricciones que se imponen en el uso de constantes se exponen a continuación:

[ÍNDICE]

Sección de dominios

En la sección DOMAINS se pueden definir dominios no especificados por defecto por Visual Prolog. Por ejemplo, el dominio integer es estándar para el compilador, no así el dominio sinónimo ENTERO o dominios complejos. En estos últimos casos, el programador debe definirlo en esta sección.

Cada sección de dominios debe comenzar con la palabra DOMAINS. Ejemplo:

DOMAINS

ENTERO = INTEGER

Es posible usar 5 formatos de declaraciones para dominios definidos por el usuario.

[ÍNDICE]

Dominios de Objetos Compuestos

El formato es como sigue:

dominio = [reference] [align {byte|word|dword}] declaracion1;[declaracion2];...

Ejemplo:

DOMAINS

LECTOR = lee(SYMBOL Nombre, LECTURA Item)

LECTURA = libro(SYMBOL Autor, SYMBOL Titulo, SYMBOL Editorial);

revista (SYMBOL Titulo, INTEGER Numero)

Los nombres colocados al lado de cada dominio en los subcomponentes de la definición de los dominios del ejemplo son opcionales y pueden omitirse, ya que sólo sirven para aumentar la legibilidad del programa.

El símbolo ";" indica que el dominio LECTURA puede referirse tanto a libros como a revistas, es decir, tanto los objetos libros como los objetos revistas pertenecen al dominio LECTURA.

El siguiente programa lista lo que cada lector lee en las variables X e Y respectivamente.

DOMAINS

LECTOR = lee(SYMBOL Nombre, LECTURA Item)

LECTURA = libro(SYMBOL Autor, SYMBOL Titulo, SYMBOL Editorial);

revista (SYMBOL Titulo, INTEGER Numero)

PREDICATES

lectores(LECTOR)

CLAUSES

lectores(lee(antonio, libro(cervantes, quijote, anaya))).

lectores(lee(pepe, revista(hola, 22))).

lectores(lee(juan, libro(delphi4, alvarez, anaya))).

GOAL

lectores(lee(X,Y)).

El resultado obtenido tras la ejecución del objetivo propuesto en la sección de metas es:

X=antonio, Y=libro("cervantes","quijote","anaya")

X=pepe, Y=revista("hola",22)

X=juan, Y=libro("delphi4","alvarez","anaya")

3 Solutions

[ÍNDICE]

Dominios de Objetos Compuestos Functorless

En estos dominios podemos definir tipos de objetos que sólo se pueden comportar como una única clase de objetos. Mientras que en el caso anterior un determinado dominio podía definir un objeto u otro, en el caso de dominios de objetos Functorless dicho dominio sólo puede definir un objeto compuesto. La sintaxis de este tipo de dominios es similar a la anterior salvo porque es necesario especificar la palabra struct y no puede haber más de una definición de objeto por dominio.

dominio = struct [align {byte|word|dword}] declaracion

Con este tipo de declaración los objetos utilizado mantienen una sintaxis muy parecida a la de C.

Ejemplo:

DOMAINS

ESCULT = struct escultura (INTEGER ns, SYMBOL autor, SYMBOL material)

PINTURA = struct cuadro (INTEGER ns, SYMBOL autor, SYMBOL tecnica)

[ÍNDICE]

Sinónimos de Dominios Estándar

En ocasiones, puede ser útil definir sinónimos de dominios que son estándar, para una mayor legibilidad del programa, por ejemplo. Así pues en la siguiente declaración:

DOMAINS

ENTERO = integer

ESCULT = struct escultura (ENTERO ns, SYMBOL autor, SYMBOL material)

PINTURA = struct cuadro (ENTERO ns, SYMBOL autor, SYMBOL tecnica)

ENTERO no es más que un sinónimo de integer que puede ser usado del mismo modo que este último.

[ÍNDICE]

Dominios tipo Lista

Las listas son estructuras comunes de datos en Prolog consideradas como una forma de objeto compuesto. Son secuencias de elementos generalmente homogéneos. Sin embargo, en Visual Prolog es fácil crear listas heterogéneas mediante el uso eficaz de declaración de dominios.

La sintaxis para crear una lista de objetos es del modo:

dominiolista = dominiocomponentes*

Si dominiocomponentes define varios objetos distintos, la lista definida será heterogénea o polimórfica.

Veamos varios ejemplo de listas homogéneas:

DOMAINS

listaenteros = integer* /* Lista de enteros */

listacaracteres = char* /* Lista de caracteres */

listacuadros = cuadro* /* Lista de estructuras del dominio cuadro */

Veamos ahora un ejemplo de lista polimórfica que puede almacenar tanto estructuras tipo cuadro como estructuras tipo escultura:

DOMAINS

objetos = escultura (ENTERO ns, SYMBOL autor, SYMBOL material);

cuadro (ENTERO ns, SYMBOL autor, SYMBOL tecnica)

/* Los objetos pueden ser esculturas o cuadros */

listapolim = objetos* /* Esto es una lista de objetos */

[ÍNDICE]

Dominios tipo Predicado

Un dominio predicado (dominio puntero a predicado), permite la declaración de un grupo o una clase de predicados. La sintaxis es como sigue:

PredicadoDom = TipoPredicado [Retorno] (ListaArg) [- [[TipoPredicado] patron]] [lenguaje]

donde:

Ejemplo:

DOMAINS

par = pares (INTEGER, INTEGER)

listapares = par*

listaenteros = integer*

unaria = determ INTEGER (INTEGER) - (i)

PREDICATES

predicadosuma(par, INTEGER)

cubo: unaria

cuadrado: unaria

operacion(listapares, unaria, listaenteros)

CLAUSES

cubo(E, ECubo):- ECubo=E*E*E.

cuadrado(E, ECuadrado):- ECuadrado=E*E.

predicadosuma(pares(E1, E2), Suma):- Suma=E1+E2.

/*---*/

operacion([],_,[]).

operacion([X|Y], OperacionUnaria, LRes):- predicadosuma(X, S),

Res=OperacionUnaria(S),

operacion(Y,OperacionUnaria,Aux),

LRes=[Res|LAux].

GOAL

operacion([pares(3,2),

pares(2,1), pares(3,4)], cuadrado, ListaCuadrados),

operacion([pares(3,2),

pares(2,1), pares(3,4)], cubo, ListaCubos).

Vamos a explicar detalladamente cómo funciona el ejemplo propuesto para ilustrar el uso de la sección DOMAINS.

El predicado principal se denomina operacion y admite como argumentos una lista de predicados del dominio listapares, un predicado del dominio OperacionUnaria y una lista de enteros. Es interesante notar que en Visual Prolog podemos pasar como argumentos de nuestros predicados, no sólo datos y objetos compuestos sino también predicados y funciones que realizan diversas operaciones. De este modo, el predicado operacion recorre cada par de la lista, suma los números que componen dicho par y les aplica el predicado (operación unaria), pasado por parámetro, así el mismo predicado operacion sirve para calcular cuadrados, cubos, etc., sobre la suma de los pares de la lista original.

Como vemos unaria = determ INTEGER (INTEGER) - (i) es la definición de un dominio al que pertenecen todos los predicados que tengan esa forma. Cualquier predicado que realiza una operación unaria de este tipo encaja en ese dominio. Así pues en el ejemplo tenemos definidos los predicados cuadrado y cubo como del dominio unaria.

En la sección CLAUSES se define exactamente el cuerpo de ambos predicados que este caso actúan como funciones.

Para la meta especificada, el resultado de la ejecución es el siguiente:

ListaCuadrados=[25,9,49], ListaCubos=[125,27,343]

1 Solution

[ÍNDICE]

Sección de la base de datos

Como sabemos un programa en Visual Prolog no es más que un conjunto de hechos y reglas. En ocasiones, podemos querer actualizar este conjunto de hechos mientras el programa se ejecuta. En tal caso, los hechos deben tratarse como un conjunto dinámico de datos que pertenecen a una base que puede ser actualizada y modificada convenientemente mientras dura la ejecución del programa.

Sólo los hechos que se declaran en la sección FACTS serán dinámicos y podrán ser actualizados en tiempo de ejecución.

La sección FACTS (también llamada DATABASE) tiene la siguiente forma:

[GLOBAL] {FACTS | DATABASE} [- nombre de la base de datos]

[nocopy][{nondeterm|determ|single}] hecho_1[([Lista_Args_hecho_1])]

...

[nocopy][{single|determ|nondeterm}] hecho_N[([Lista_Args_hecho_N])]

...

donde:

[ÍNDICE]

Predicados definidos en Visual Prolog para manejar la base de datos

Veamos ahora algunos ejemplos de uso de los predicados mostrados para manejar la base de hechos.

FACTS

padre(string, string)

PREDICATES

abuelo(string, string)

CLAUSES

padre(juan, pepe).

padre(juan, luis).

padre(pepe, manolo).

abuelo(X, Y):-padre(X, Z), padre(Z, Y).

GOAL

assert(padre(pepe, beatriz)),

assertz(padre(pepe, carlos)),

asserta(padre(pepe, maria)),

abuelo(juan, Y).

Tenemos 3 hechos que definen quién es padre de quién y un predicado para deducir el parentesco abuelo.

Tras la ejecución de la meta obtenemos el siguiente resultado:

Y=maria

Y=manolo

Y=beatriz

Y=carlos

4 Solutions

Si examinamos paso a paso esta ejecución, lo que ha sucedido ha sido lo siguiente:

Veamos ahora un ejemplo del uso de retract y retractall.

FACTS

padre(string, string)

PREDICATES

abuelo(string, string)

CLAUSES

padre(juan, pepe).

padre(juan, luis).

padre(pepe, manolo).

padre(pepe, beatriz).

padre(pepe, carlos).

padre(pepe, maria).

abuelo(X, Y):-padre(X, Z), padre(Z, Y).

GOAL

retract(padre(pepe, _)), !,

padre(pepe, L).

En este caso retract borra todas la primera ocurrencia de padre, siempre que su primer argumento sea pepe.

El corte que va después de retract sirve para que Visual Prolog no haga backtracking y borre todas las ocurrencias de padre(pepe, ...), ya que por defecto el compilador intenta ofrecer todas las posibles soluciones para una meta dada. Con el corte situado en ese lugar, sólo se hace bactracking sobre padre(pepe, L).

El resultado de la ejecución se muestra a continuación:

L=beatriz

L=carlos

L=maria

En el ejemplo siguiente, vemos que en lugar de usar retract estamos usando retractall para borrar todas las ocurrencias de padre(pepe, ...).

FACTS

padre(string, string)

PREDICATES

abuelo(string, string)

CLAUSES

padre(juan, pepe).

padre(juan, luis).

padre(pepe, manolo).

padre(pepe, beatriz).

padre(pepe, carlos).

padre(pepe, maria).

abuelo(X, Y):-padre(X, Z), padre(Z, Y).

GOAL

retractall(padre(pepe, _)),

padre(pepe, L).

El resultado de esta ejecución es:

No Solution

[ÍNDICE]

Sección de definición de predicados

En esta sección se definen los predicados que pueden pertenecer a dominios definidos en la sección DOMAINS (punteros a predicados), o se definen los predicados con una forma determinada en caso de que no los hayamos agrupado en ningún dominio concreto (todas las reglas de la sección de cláusulas deben pertenecer a algún predicado). La sección de definición de predicados va precedida siempre de la palabra PREDICATES.

Por ejemplo podemos tener este caso:

DOMAINS

unaria = determ INTEGER (INTEGER) - (i)

PREDICATES

cubo: unaria

CLAUSES

cubo(E, Res):-Res=E*E*E.

O podemos tener este otro:

PREDICATES

cubo(INTEGER, INTEGER)

CLAUSES

cubo(E, Res):-Res=E*E*E.

En el primer caso cubo se define como una función y debe ser llamada como A=cubo(3) y en el segundo caso la llamada debe hacerse como cubo(3, A).

Ambas formas de definición son igualmente correctas y su uso dependerá de la aplicación en cuestión en la que estemos trabajando.

[ÍNDICE]

Tipos de predicados

Como hemos visto, pueden existir 6 tipos de predicados en función de cómo y cuantas soluciones pueden aportar y si pueden o no fallar. Cada predicado puede tener varias cláusulas o reglas que lo implementa, por tanto, cláusulas y predicados deben ser coherentes.

Esta coherencia está relacionada con el concepto de determinismo en Visual Prolog.

La mayoría de los lenguajes son deterministas. Esto quiere decir que cualquier conjunto de datos de entrada conduce a un único conjunto de instrucciones para producir un conjunto de datos de salida. Sin embargo, en Visual Prolog se admite la inferencia no determinista basada en predicados no deterministas.

Visual Prolog ejerce un gran control sobre el determinismo de las reglas y predicados que aparecen en los programas, principalmente con el objeto de ahorrar espacio de almacenamiento en tiempo de ejecución. Cuando una cláusula o regla determinista que compone un predicado termina con éxito, el espacio de pila correspondiente puede ser dispensado rápidamente, liberando de este modo el espacio ocupado.

El sistema de control del determinismo de Visual Prolog es muy fuerte. De hecho, el compilador fuerza al programador, a través de este control, a declarar en sus predicados 2 aspectos de comportamiento importantes:

De acuerdo a estos dos aspectos de determinismo, Visual Prolog soporta los siguientes tipos de predicados:

El sistema de control de determinismo de Visual Prolog chequea la correspondencia de las definiciones de predicados con los modos de determinismo declarados para los predicados. Por defecto, el compilador verifica las cláusulas o reglas de predicados y da un warning si no puede garantizar que un predicado declarado como multi, procedure o erroneous nunca falla.

Todos los predicados se definen internamente con alguno de estos modos. Para los predicados definidos como determ, procedure, failure o erroneous el compilador dará un warning para cada cláusula o regla del programa que dé como resultado un predicado no determinista.

Hay dos tipos de cláusulas no deterministas:

Cuando se escriben predicados declarados como multi, procedure o erroneous se deben seguir un par de reglas:

[ÍNDICE]

Ejemplos de cómo conseguir la coherencia entre reglas y predicados en el aspecto determinista

Como hemos dicho hay dos tipos de cláusulas no deterministas: uno de los tipos son aquellas que no contienen un corte y hay una o más cláusulas que pueden casar (o unificarse) con los mismos argumentos de entrada para ese patrón de flujo de entrada. Veamos un ejemplo de predicado definido como determinista con un conjunto de cláusulas que no son deterministas:

DOMAINS

list=integer*

PREDICATES

lista(list)

prueba

CLAUSES

lista([]).

lista([1]).

lista([1,2]):-write("Hola").

lista(L):-write(L).

prueba:-lista([1,2]),

nl,

lista([1,2,3]).

GOAL

prueba.

Como vemos el conjunto de reglas para el predicado lista(list) son todas no deterministas, ya que no aparece un corte en ellas y hay varias que pueden casar con el mismo patrón de entrada. Por ejemplo, la lista vacía puede casar en lista([]) y en lista(L).

Para resolver la incoherencia podemos tomar dos soluciones:

Vamos a suponer que forzamos el fallo tal y como tenemos actualmente escrito el programa, para ello la única variación que hay que hacer es la siguiente:

prueba:-lista([1,2]),

nl,

lista([1,2,3]),

fail.

La ejecución del programa produce el siguiente resultado:

Hola

[1,2,3][1,2]

[1,2,3]

Veamos el árbol de ejecución que se ha generado en la Figura 1:

Figura 1

Árbol de ejecución

Nos interesa que el programa aporte una única solución y no un conjunto de ellas, incluso si intentamos hacer backtracking de un modo forzado. El corte nos proporciona capacidad para podar el árbol de ejecución, por tanto, hemos de poner tantos cortes como se requieran para conseguir que el programa definido por el conjunto de reglas aporte una solución única en cualquier caso.

La modificación realizada, la ejecución del programa y el árbol de ejecución se presentan a continuación, ver Figura 2:

lista([]):-!.

lista([1]):-!.

lista([1,2]):-write("Hola"),!.

lista(L):-write(L).

Hola

[1,2,3]

Figura 2

Árbol de ejecución

[ÍNDICE]

Sección de cláusulas

La sección de cláusulas contiene la especificación o implementación del conjunto de hechos y reglas que componen el programa. Dicha sección se encabeza con la palabra CLAUSES.

Una cláusula puede ser:

Una secuencia de cláusulas que definen un predicado se denomina procedimiento.

[ÍNDICE]

Sección de meta u objetivo

En esta sección se coloca la meta u objetivo que deseamos que Prolog satisfaga. Es similar a cualquier otra regla, ya que la meta está compuesta de un conjunto de submetas que hay que demostrar, la principal diferencia reside en que detrás de la palabra GOAL no se pone ":-", es decir no existe parte izquierda de la regla, y ésta se ejecuta directamente cuando arranca el programa.

[ÍNDICE]

Ejemplo

[CÓDIGO FUENTE] [EJECUTABLE]

Vamos a ver un ejemplo completo de cómo realizar un programa en Visual Prolog haciendo uso de todas las secciones que hemos visto.

El programa debe gestionar una lista polimórfica de obras de arte. Las obras de arte pueden ser cuadros o esculturas. Los datos que se deben almacenar en un cuadro son el número de serie, el autor, el estilo y las dimensiones. Los datos que se deben almacenar en una escultura son el número de serie, el autor y el material.

El programa permitirá insertar en orden las obras y visualizará listados de cuadros y esculturas de forma separada.

Comencemos, pues a examinar el código construido.

DOMAINS

obra = cuadro (INTEGER, STRING, STRING, STRING);

escultura (INTEGER, STRING, STRING)

LIST = obra*

En la sección DOMAINS definimos el dominio obra, que agrupará dos tipos de objetos compuestos: cuadros o esculturas.

El dominio LIST define el tipo lista polimórfica de obras de arte.

Gráficamente tenemos:

PREDICATES

obtenerNS(INTEGER, obra)

insorden(obra,LIST,LIST)

pedirdatoscuadro(STRING, STRING)

pedirdatosescultura(STRING)

insertar(LIST, LIST, CHAR, INTEGER, STRING)

inserta(LIST, LIST)

eligeobra(CHAR, INTEGER, STRING)

escribircuadro(obra)

escribirescultura(obra)

nondeterm recorrerlista(LIST, SYMBOL)

nondeterm ejecutaropcion(INTEGER, LIST, LIST)

programa(LIST)

menu(INTEGER)

En la sección PREDICATES hemos definido el nombre y el tipo de los argumentos de todos los predicados que se usarán durante el programa.

En la sección CLAUSES tenemos el conjunto de reglas o cláusulas que implementan cada predicado.

Vamos a describir brevemente lo que hace cada regla.

CLAUSES

obtenerNS(Serie,Predicado):-Predicado=cuadro(Serie,_,_,_), !.

obtenerNS(Serie,Predicado):-Predicado=escultura(Serie,_,_).

La regla obtenerNS tiene dos parámetros: un INTEGER y una obra. El segundo parámetro es de entrada y el primer parámetro es de salida. Cuando unificamos la variable Predicado que tras la llamada a la regla estará instanciada, ésta almacenará o un cuadro o una escultura. Si almacena un cuadro la comparación Predicado=cuadro(Serie,_,_,_) será cierta y la variable Serie se instanciará con el valor del número de serie del cuadro. Si almacena una escultura, la primera regla fallará y pasaremos a comparar el Predicado con el objeto escultura(Serie,_,_). La unificación será posible ya que si no es un cuadro es una escultura y en este caso estaremos obteniendo el número de serie de una escultura.

insorden(E,[],[E]):-!.

insorden(E,[X|Y],[X|Cola]):-obtenerNS(Serie1,E),

obtenerNS(Serie2,X),

Serie1>Serie2,

insorden(E,Y,Cola), !.

insorden(E,LOr,[E|LOr]).

El predicado insorden admite tres parámetros: un elemento de entrada de tipo obra y otro de tipo LIST y por último un elemento de salida también de tipo LIST.

El predicado está implementado mediante tres reglas:

pedirdatoscuadro(Estilo, Dimensiones):-nl, write("Estilo: "),

readln(Estilo),

write("Dimensiones: "),

readln(Dimensiones).

El predicado pedirdatoscuadro acepta dos parámetros de salida: el estilo y las dimensiones del cuadro. La regla que lo implementa simplemente rellena las variables con los contenidos capturados a través del teclado.

pedirdatosescultura(Material):-nl, write("Material: "),

readln(Material).

El predicado pedirdatosescultura acepta un parámetro de salida: material de la escultura. La regla que lo implementa simplemente rellena la variable con el contenido capturado a través del teclado.

insertar(L,NuevaL,Seleccion, NS, Autor):-Seleccion='c',

pedirdatoscuadro(Estilo, Dimensiones),

Registro=cuadro(NS, Autor, Estilo, Dimensiones),

insorden(Registro,L,NuevaL), !.

insertar(L,NuevaL,Seleccion, NS, Autor):-Seleccion='e',

pedirdatosescultura(Material),

Registro=escultura(NS, Autor, Material),

insorden(Registro,L,NuevaL).

El predicado insertar acepta cinco parámetros, todos de entrada salvo NuevaL que es de salida. Las reglas que implementan este predicado funcionan de modo similar. Si el parámetro Seleccion contiene el carácter "c" en el objetivo propuesto, ejecutaremos la primera regla, de modo que estaremos insertando un cuadro. Si el parámetro Seleccion contiene el carácter "e", ejecutaremos la segunda regla, de modo que estaremos insertando una escultura en la lista NuevaL.

eligeobra(Seleccion, NS, Autor):-write("Escribe el Número de Serie: "),

readint(NS),

write("Escribe el Nombre del Autor: "),

readln(Autor),

write("Selecciona [c]=Cuadro o [e]=Escultura -> "),

readchar(Seleccion).

El predicado eligeobra acepta tres parámetros de salida. En la regla que lo implementa las variables se rellenan con los contenidos obtenidos a través del teclado.

inserta(L, NuevaL):-eligeobra(Seleccion, NS, Autor),

insertar(L,NuevaL,Seleccion, NS, Autor).

El predicado inserta acepta dos parámetros de tipo LIST, el primero de entrada y el segundo de salida. La regla que lo implementa se satisface mediante la resolución de la regla eligeobra y posteriormente con una llamada a insertar con los resultados obtenidos de la resolución de la regla precedente.

escribircuadro(cuadro(NS,Autor,Estilo,Dimensiones)):-

write("Obra de Arte => CUADRO"), nl,

write("Número de Serie: "), Aux1=NS, write(Aux1), nl,

write("Autor: "), Aux2=Autor, write(Aux2), nl,

write("Estilo: "), Aux3=Estilo, write(Aux3), nl,

write("Dimensiones: "), Aux4=Dimensiones, write(Aux4), nl,

write("---------------------------------"), nl,

write("Pulsa para continuar..."),

readint(_).

El predicado escribecuadro acepta por parámetro un elemento de tipo obra, luego la regla que lo implementa acepta como parámetro un objeto compuesto de tipo cuadro que está englobado dentro del dominio obra. Esta regla simplemente actualiza los parámetros del objeto compuesto con los contenidos obtenidos desde teclado.

escribirescultura(escultura(NS, Autor, Material)):-

write("Obra de Arte => ESCULTURA"), nl,

write("Número de Serie: "), Aux1=NS, write(Aux1), nl,

write("Autor: "), Aux2=Autor, write(Aux2), nl,

write("Material: "), Aux3=Material, write(Aux3), nl,

write("---------------------------------"), nl,

write("Pulsa para continuar..."),

readint(_).

El predicado escribeescultura acepta por parámetro un elemento de tipo obra, luego la regla que lo implementa acepta como parámetro un objeto compuesto de tipo escultura que está englobado dentro del dominio obra. Esta regla simplemente actualiza los parámetros del objeto compuesto con los contenidos obtenidos desde teclado, como en el caso anterior.

recorrerlista([], _):-!.

recorrerlista([X|Y],Que):- Que=cu,

escribircuadro(X),

recorrerlista(Y, Que),!.

recorrerlista([X|Y],Que):- Que=es,

escribirescultura(X),

recorrerlista(Y, Que),!.

recorrerlista([_|Y],Que):-recorrerlista(Y, Que).

El predicado recorrerlista acepta dos parámetros de entrada. Está implementado mediante cuatro reglas:

ejecutaropcion(0,L,L):-recorrerlista(L,cu),!.

ejecutaropcion(1,L,L):-recorrerlista(L,es),!.

ejecutaropcion(2,L,NuevaL):-inserta(L,NuevaL).

El predicado ejecutaropcion admite 3 parámetros, de los cuales los dos primeros son de entrada y el último de salida. Tres posibles reglas implementan el predicado:

menu(Opcion):-write("------------ MENU ------------"),nl,

write("- 0.- Listado de Cuadros -"),nl,

write("- 1.- Listado de Esculturas -"),nl,

write("- 2.- Insertar elementos -"),nl,

write("- 3.- Salir -"),nl,

write("------------------------------"),nl,

write("Opción: "),

readint(Opcion),

write("---------------------------------"), nl.

El predicado menu acepta un parámetro de salida. La regla que lo implementa muestra al usuario el conjunto de posibles opciones y almacena en la variable Opcion el contenido seleccionado a través de teclado.

programa(L):-menu(Opcion),

Opcion<>3,

ejecutaropcion(Opcion,L,NuevaL), !,

programa(NuevaL),!.

programa(_):-write("Hasta pronto..."), nl.

El predicado programa admite como parámetro de entrada una lista de obras de arte. Está implementado mediante dos reglas:

GOAL

programa([]).

La meta definida es una llamada a programa pasándole por parámetro una lista vacía, esta llamada se unificará con la primera regla del predicado programa y dispondremos ya de la variable local L iniciada para comenzar a trabajar con nuestra lista de obras de arte.

[ÍNDICE]

El entorno de desarrollo de Visual Prolog

El entorno de desarrollo de Visual Prolog nos proporciona la capacidad de generar aplicaciones lógicas orientadas a eventos para Windows 95 y 98 de forma sencilla. Disponemos de capacidades de creación de programas de forma visual y esto implica que el código que implementa la interfaz de usuario sea generado en su mayor parte por el propio Experto del entorno de desarrollo.

De lo que se tiene que encargar el programador, por tanto, es de especificar las tareas que se deben llevar a cabo como respuesta a los sucesos o eventos que se producen en el sistema.

Antes de profundizar en el estudio de este entorno, demos un repaso breve a los conceptos sobre los que se apoya la programación orientada a eventos.

[ÍNDICE]

Aplicaciones orientadas a eventos

En una aplicación DOS, el programador fija la secuencia de operaciones en tiempo de diseño. No existe apenas interacción entre la aplicación diseñada y otras aplicaciones y el sistema operativo no produce interferencias.

Las aplicaciones DOS suelen comunicarse directamente con el usuario y se basan en una filosofía de implementación estática.

Los sistemas Windows, en cambio, soportan una filosofía dinámica. En estos casos, el sistema operativo provee a las aplicaciones de una capa de servicios para su comunicación con el usuario, de este modo el sistema operativo se convierte en un objeto activo que el programador debe conocer ya que participa en la ejecución y secuenciación de las aplicaciones dentro de un ámbito multitarea. Las aplicaciones deben contemplar esta característica y la metodología de construcción de las mismas se denomina diseño orientado a eventos.

Cuando el usuario realiza una operación para interactuar con el sistema (pulsar una tecla, mover el ratón,...), se produce un suceso o evento. Dicho evento es generado desde el interfaz hardware y el sistema operativo lo traduce a un mensaje con formato estándar y envía una notificación de evento al propietario correcto que generalmente es la ventana activa (la que tiene el foco).

Los eventos pueden llegar a la aplicación de forma asíncrona, por ello es necesario escribirla cuidadosamente, notando en cada momento que nos movemos en un entorno multitarea donde el resto de aplicaciones y el sistema operativo son objetos plenamente activos, para conseguir que se mantenga la integridad tanto de procesos como de datos.

Para cada evento debe existir un manejador de evento, que contiene el código que se ha de ejecutar tras producirse el suceso al que se encuentra vinculado.

Cuando el programador escribe el programa asocia un manejador a cada evento que desea que se trate. Los manejadores, normalmente, son rutinas con una cabecera preestablecida y un cuerpo vacío que debe ser rellenado con las instrucciones apropiadas.

Si el programador no define un manejador propio para un evento determinado, dicho evento es tratado por defecto por el sistema.

[ÍNDICE]

Aplicaciones orientadas a eventos en Visual Prolog

La interfaz de un programa en Visual Prolog va a ser simplemente un conjunto de ventanas de aspectos y tipos distintos organizadas mediante una jerarquía.

La ventana principal se denomina ventana de tareas o Task Window.

Cada ventana tiene un número que la identifica dentro del sistema, ese número se denomina manejador (handler) de la ventana.

Dado que las ventanas se encuentran organizadas en un sistema jerárquico podemos hablar de que cada ventana tiene un padre.

Cada ventana lleva asociado un predicado que maneja todos los eventos para la misma. Dicho predicado pertenece al dominio EHANDLER y tiene dos argumentos importantes:

Además los predicados del dominio EHANDLER pueden devolver valores a través de un parámetro de retorno del dominio LONG que se usan en muy pocos casos. Normalmente, se devuelve 0.

El entorno de desarrollo de Visual Prolog proporciona un diálogo llamado Experto a partir del cual se generará parte del código que tiene que ver con la implementación de la interfaz de la aplicación.

Los predicados manejadores de evento para cada ventana deben ser generalmente declarados y creados a través del Experto. El programador debe escribir personalmente el código a incluir en el cuerpo de los manejadores de evento ya declarados por el Experto para los eventos que desee controlar de forma explícita. El resto de eventos serán manejados por defecto por el sistema.

[ÍNDICE]

Tipos de ventanas en Visual Prolog

Como hemos comentado anteriormente, la interfaz de una aplicación no es más que un conjunto de ventanas organizadas de modo jerárquico.

Sin embargo, no todas las ventanas tienen la misma finalidad: un botón no es lo mismo que un diálogo.

Veamos los diferentes tipos de ventanas que existen:

Windows: Existen distintos tipos de ventanas dependiendo del lugar que ocupen en la jerarquía, Tabla 2.

Tipo

Descripción

Task Window

Es una abstracción de la aplicación en sí misma. Es la ventana principal del programa y la inicialización y finalización de éste están siempre asociados a dicha ventana. Bajo el modo MDI (Multiple Document Interface) la ventana principal actúa como contenedor de las ventanas hijas.

Screen Window

Es una abstracción que representa la pantalla entera. Esta ventana siempre es el padre de la Task Window.

Top-Level Window

Se trata de un documento normal de Windows cuyo padre puede ser la Task Window o la Screen Window. Pueden almacenar ventanas hijas y controles y, a menudo, contienen menús y pueden ser maximizadas y minimizadas.

Child Windows

Están restringidas a permanecer dentro del su ventana padre. Cuando la ventana padre es cerrada, destruida, minimizada, etc., también lo son las ventanas hijas.

Print

Ventana interna usada durante el proceso de impresión

Picture

Ventana interna usada durante el proceso de creación de un gráfico, imagen o dibujo.

Metafile

Ventana interna usada durante la creación de un metafile.

Tabla 2

Dialogs: Los diálogos son un tipo especial de ventanas, cuya funcionalidad está reducida con respecto a las ventanas normales. Los diálogos normalmente se rellenan con controles, que puede interactuar con el usuario para visualizar salidas, aceptar entradas, ofrecer métodos de selección de opciones o permitir la edición de texto. Los diálogos pueden ser de dos tipos, Tabla 3:

Tipo

Descripción

Diálogo modal

Hasta que el diálogo no es cerrado no se puede acceder a las ventanas de la aplicación que se encuentran debajo de dicho diálogo.

Diálogo no modal

Es posible acceder a lo que está debajo del diálogo antes de haberlo cerrado.

Tabla 3

Controls: Son pequeñas ventanas con una apariencia especial y una funcionalidad fija proporcionada por el sistema operativo. Se usan dentro de conjuntos fijos de controles dentro de otras ventanas, principalmente como componentes de diálogos. Los controles proveen al usuario la capacidad de escribir texto, llamar a comandos o elegir opciones. Los eventos que se generan por la manipulación de los controles causan notificaciones de mensajes que se envían al manejador de eventos de la ventana padre. Existen varios tipos de controles, Tabla 4:

Tipo

Descripción

PushButton

Botón típico que puede apretarse y soltarse.

RadioButton

Botón de aspecto redondo que suele estar agrupado con otros de la misma clase y sólo uno de ellos permanece activo simultáneamente.

CheckBox

Botón de aspecto cuadrado que puede estar o no marcado.

HScroll

Barra de scroll horizontal.

VScroll

Barra de scroll vertical.

Edit

Cuadro o ventana de edición de texto.

Text

Etiqueta de texto estático.

LBox

Lista de opciones o cadenas que pueden ser seleccionadas.

LBoxButton

Lista desplegable de opciones.

LBoxEdit

Lista desplegable de texto que puede editarse.

Icon

Icono.

Custom

Control personalizado.

Tabla 4

Los identificadores de los tipos de ventanas pertenecen al dominio WINTYPE y tienen w_ delante del nombre especificado para las ventanas, wc_ para los controles y wd_ para los diálogos.

Cuando se crea cualquier tipo de ventana o diálogo existen varios flags que determinan la apariencia del diálogo y que permiten conocer el estado del objeto sobre el que se está actuando.

[ÍNDICE]

El diálogo Experto del entorno de desarrollo VPI

Cuando desarrollamos cualquier aplicación en Visual Prolog el proceso de generación de los ficheros que van a componer el programa se realiza siempre de un modo similar.

Para ello accedemos a la opción New Project del menú principal Project, ver Figura 3 .

El diálogo que aparece nos permite configurar el método de desarrollo de nuestra aplicación de una forma muy sencilla. Los pasos a seguir son los siguientes:

  1. Establecer un nombre para la aplicación en la caja de texto Project Name.
  2. Seleccionar un directorio base para la aplicación mediante la utilización de la caja de texto Base Directory.
  3. Accionar la pestaña Target. A través de ella se configura el tipo de plataforma donde se ejecutará el programa, el tipo de estrategia de interfaz de usuario que se utilizará, si lo que se pretende generar es una aplicación exe o una dll y por último, seleccionar el lenguaje de programación que se usará para generar el programa principal. De no seleccionar VPI como estrategia de generación de la interfaz de usuario las opciones de la pestaña VPI Options aparecerán desactivadas, en caso contrario se permitirá seleccionar el conjunto de paquetes que se incluirán en la aplicación para montar la interfaz.
  4. Accionar el resto de pestañas para configurar otros objetos más avanzados como el tipo de generación de código, la inclusión de objetos para manejar bases de datos, la introducción de los datos personales del programador y la colocación de elementos de ayuda en la aplicación.

Figura 3

Aspecto del Experto de creación de una aplicación

Si se genera una aplicación sin entorno VPI, el método de programación no será ni visual ni orientado a eventos y deberemos comportarnos como en la escritura de cualquier aplicación realizada para DOS.

Vamos a considerar, sin embargo, aplicaciones con entorno VPI por tanto, lo primero que debemos saber es que VPI integra un conjunto de librerías en lenguaje Prolog que podemos usar y llamar para utilizar los servicios de interfaz proporcionados por el sistema Windows. Nos interesa conocer el uso de VPI, no cómo está construido por dentro.

La arquitectura en la que nos vamos a mover se muestra a continuación:

Conocemos, hasta ahora, el concepto de programación orientada a eventos, la estructuras de Prolog, los tipos de ventanas que ofrece VPI y la arquitectura de comunicación entre aplicación-VPI-Windows. También conocemos como crear una aplicación en vacío. Veamos, a continuación, cómo se utiliza el Diálogo Experto para colocar, configurar y crear las cabeceras de los manejadores de evento de los elementos implicados en la interfaz y funcionamiento de la aplicación con un ejemplo sencillo.

[ÍNDICE]

El primer programa VPI

[FUENTES EN FORMATO ZIP] [EJECUTABLE EXE]

Nuestro primer programa VPI va a ser el sistema experto de cálculo de velocidades en función de las características de la vía y el vehículo que hemos estudiado en teoría. Vamos a especificar, a continuación, todos los pasos de diseño e implementación a realizar.

Nuestro diseño descendente se muestra en el siguiente gráfico:

El diseño de la interfaz de usuario puede ser como se muestra a continuación.

La pantalla principal mostrará dos apartados:

Cuando se pulsa el botón Calcular aparece un diálogo que muestra la velocidad para esa configuración de vía y vehículo.

Ya tenemos el diseño descendente y el diseño de pantallas, pasemos ahora a la implementación del programa.

Lo primero es crear con el Experto una aplicación en vacío con todos los componentes VPI necesarios, en este caso: Task Window con menú y la posibilidad de generar diálogos.

A continuación aparece el siguiente diálogo:

En la parte izquierda tenemos activos los botones que nos permiten crear nuevos componentes para la aplicación. En la parte derecha aparecen los botones que permiten editar y borrar componentes añadidos, así como editar sus atributos, y acceder a la ventana de código experto que nos permitirá crear fácilmente las cabeceras de nuestros manejadores de evento.

Si ejecutamos la aplicación aparece una ventana vacía con tres elementos de menú: File, Edit y Help. Como ninguno de esos elementos nos interesa los vamos a eliminar y pondremos en su lugar una pestaña con el nombre Menú que va a contener la opción Salir.

Para ello pulsamos sobre el botón Menu del Experto y seleccionamos Task Menu con doble click. A través del diálogo que sigue, eliminamos las opciones no interesantes.

Antes de eliminar las opciones del menú nos tenemos que asegurar de eliminar también todo el código asociado a cada evento producido por la selección de cualquiera de las opciones. Para ello es necesario observar la ventana de Código Experto y buscar en ella los eventos asociados al menú para eliminar las cláusulas manejadoras introducidas automáticamente por el sistema.

Si seleccionamos en el Diálogo Experto la Task Window y hacemos doble click obtenemos un panel y una serie de componentes que podemos pegar sobre la ventana.

Controls nos proporciona un conjunto de controles que podemos pegar en la ventana. Layout provee herramientas para colocar los elementos dentro de la ventana.

La ventana principal de nuestra aplicación tiene el siguiente aspecto tras haber pegado los controles adecuados, en el esquema mostrado se especifica los nombres de los controles dinámicos, es decir, aquellos que van a permitir interactuar al usuario con el sistema. El resto de controles son etiquetas estáticas de información.

Vemos, ahora, el diálogo ya diseñado que debe aparecer tras la pulsación del botón Calcular. Posee tres elementos: dos etiquetas, una de información y otra para volcar el valor de la velocidad en ella y un botón de aceptación. Como el diálogo es modal, hasta que no lo cerremos no podremos actuar sobre la ventana principal.

Ya tenemos el diseño de los formularios preparados. Con sólo pegar los elementos en la ventana principal se ha ido incluyendo el código necesario para que los controles funcionen adecuadamente. Necesitamos además incluir el código del diálogo en el fichero del programa. Para ello accedemos a su código experto y especificamos que deseamos que dicho código se incluya en el fichero adecuado que será, en este caso, el archivo autowin.pro.

Como se observa, el código debe actualizarse en autowin.pro a partir de la primera vez que se haya pulsado el botón Update Code.

Cada vez que haya que generar un manejador de evento o editar uno ya existente podemos utilizar este tipo de diálogo para cualquier ventana que tengamos definida.

Vamos a observar ahora cual es la jerarquía de ventanas del programa y cuáles son las dependencias entre elementos.

El manejador de ventana de la Task Window recoge todos los eventos y se encarga de probar los subobjetivos correspondientes en función del evento pasado por parámetro, que puede provenir del botón Calcular o de la pulsación del menú.

El manejador del diálogo recoge los eventos de la ventana que lo enmarca y del botón de aceptación.

Nosotros debemos incluir código al manejar el evento de creación de la Task Window, en el evento de pulsación del botón Calcular y en el evento de creación del Diálogo. El resto de eventos son manejados automáticamente por el sistema.

[ÍNDICE]

Manejo del evento de creación de la Task Window.

El código que hemos de añadir es el que está resaltado en cursiva:

%BEGIN Task Window, e_Create

task_win_eh(_Win,e_Create(_),0):-!,

%BEGIN Task Window, InitControls, 14:04:04-4.10.2000, Code automatically updated!

...

...

...

%BEGIN Inicialización de la LIST BOX

W=win_GetCtlHandle(_Win, id_senales),

L=["autopista","autovia","viarapida","de_via_urbana","de_travesia","nohay"],

lbox_Add(W, L),

%END Inicialización de la LIST BOX

!.

%END Task Window, e_Create

Como la ListBox es hija de la Task Window y el predicado lbox_Add añade una lista de cadenas a la ListBox siempre que conozcamos el manejador del control, necesitamos obtener el manejador del control a través del manejador del padre _Win que viene pasado por parámetro. Para ello utilizamos el predicado winGetHandle que devuelve el manejador del control a partir del manejador de ventana del padre y del identificador del control que en este caso es id_senales.

Rellenamos una lista con los items deseados y se la pasamos a lbox_Add.

[ÍNDICE]

Manejo del evento de pulsación del botón Calcular.

%BEGIN Task Window, idc_calcular _CtlInfo

%Manejador de código para la pulsación del botón

task_win_eh(_Win,e_Control(idc_calcular,_CtrlType,_CtrlWin,_CtlInfo),0):-

W_lbox=win_GetCtlHandle(_Win, id_senales),

lbox_GetSel (W_lbox, LS, _), %Obtención del tipo de carretera

LS=[Senal|_],

W_CBox1=win_GetCtlHandle(_Win, idc_más_de_1_carril),

Mas_de_1_carril=win_IsChecked (W_CBox1), %Obtención del número de carriles

W_CBox2=win_GetCtlHandle(_Win, idc_carril_de_adelantamiento),

Carril_de_adelantamiento=win_IsChecked (W_CBox2), %Obtención de inf. sobre carril de adelantamiento

W_Edicion=win_GetCtlHandle(_Win,idc_editaarcen),

ArcenStr=win_GetText (W_Edicion),

str_int(ArcenStr,Arcen), %Obtención del tamaño del arcén

tipovehiculo(_Win, Veh), %Obtención del tipo de vehículo

obtenernumerocarriles(Mas_de_1_carril, Carril_de_adelantamiento, Carriles),

velocidad(Veh, Arcen, Carriles, Senal, VelocidadMaxima),

assert(la_velocidad(VelocidadMaxima), misdatos),

dlg_calculo_Create(_Win),

!.

task_win_eh(_Win,e_Control(idc_calcular,_CtrlType,_CtrlWin,_CtlInfo),0):-dlg_Error("Selecciona adecuadamente los parámetros"),!.

%END Task Window, idc_calcular _CtlInfo

La cabecera del evento ha sido generada por el sistema:

task_win_eh(_Win,e_Control(idc_calcular,_CtrlType,_CtrlWin,_CtlInfo),0)

y el objeto e_Control(idc_calcular,_CtrlType,_CtrlWin,_CtlInfo) nos informa de que ha sido el botón el que ha producido el evento.

Este evento se va a tratar de dos maneras, si la primera falla es porque el programa no puede hacer los cálculos correctamente y alguna cláusula falla debido a una mala introducción de los datos por parte del usuario, por tanto la máquina Prolog busca la siguiente cláusula para satisfacer que en este caso es la que visualiza un pequeño diálogo de error.

Cuando el sistema genera automáticamente la cabecera de un manejador de evento, la forma del cuerpo suele ser generalmente vacía:

cabecera(argumentos):-!, #punto para insertar código#

!.

Se puede observar que hay dos cortes uno al principio y otro al final. Esto debe dejarse así si el manejo de eventos se realiza con una sola cláusula, sin embargo si se realiza con varias como es el caso de Calcular hay que eliminar el primer corte para permitir la búsqueda de nuevas soluciones a través del proceso de backtracking.

Los subobjetivos planteados en la primera cláusula que compone el manejador, recogen los datos de los controles de la ventana principal, que el usuario debe haber rellenado. Una vez que los tiene, ejecuta el predicado velocidad que ya vimos en el caso de la aplicación no VPI en teoría. El predicado devuelve un integer en la variable local VelocidadMaxima.

Como en Prolog no existen las variables globales no podemos pasar el contenido de dicha variable a la etiqueta que debe visualizarlo en el diálogo de un modo sencillo. Podemos intentar conseguir el manejador del diálogo a través de su padre, y a continuación intentar obtener el manejador del control a través del obtenido del diálogo, aunque esta opción es un poco complicada.

Otra posibilidad es hacer uso de hechos que pueden ser generados dinámicamente. Podemos generar un hecho del tipo la_velocidad(INTEGER) que sea single. Así pues en el manejador de botón almacenamos un hecho con la velocidad calculada mediante assert, y en el evento de creación del diálogo podemos capturar esta velocidad extrayendo el argumento del hecho almacenado.

[ÍNDICE]

Manejo del evento de creación del Diálogo de Cálculo.

%BEGIN calculo, e_Create

dlg_calculo_eh(_Win,e_Create(_CreationData),0):-!,

la_velocidad(V),

W_Texto=win_GetCtlHandle(_Win, idct_calculo_1),

str_int (V_text, V),

win_SetText(W_Texto, V_Text),

!.

%END calculo, e_Create

El resto de código está perfectamente comentado e implementado en el fichero fuente que podéis encontrar en la página de la asignatura.

El aspecto de la aplicación diseñada se muestra a continuación: