Uso del API de Windows: Introducción a las Ventanas (V)

27/03/2020

 

Índice

 

Después de haber visto en los artículos anteriores los principios básicos sobre las llamadas al API de Windows desde Visual FoxPro (enero) y las funciones para el uso de ficheros .INI (febrero y marzo), vamos a seguir con las funciones para el manejo de ventanas que iniciamos el mes pasado (abril). En este caso nos fijaremos en como manejar las ventanas de otras aplicaciones.

Conocer la lista de aplicaciones arrancadas

En ocasiones nuestra aplicación deberá actuar en conjunción con otras aplicaciones dentro de Windows. Para este tipo de interacciones es interesante saber si la otra aplicación ya está arrancada. Por ejemplo, si vamos a abrir un canal DDE o una conversación por medio de OLE Automation podemos saber si la otra aplicación ya está arrancada.

Para ello utilizaremos la misma técnica que describimos el mes pasado para descubrir si nuestra aplicación ya estaba arrancada, pero en este caso crearemos una matriz con dos columnas, la primera con todos los títulos de las ventanas de las aplicaciones y las segunda con los handles de las mismas (pues nos serán de utilidad más adelante). Nosotros deberemos buscar dentro de esta matriz si la aplicación que buscamos está arrancada.

Para usar la función utilizaremos un código similar a este:

  ? aTask( "aVentanas", .F. )

Con esta llamada conseguiremos una matriz pública con los nombres de todas las ventanas principales. Si pasamos como segundo parámetro una valor verdadero (.T.) obtendremos también las ventanas ocultas de Windows. La función devuelve el número de ventanas encontradas.

     *** Constantes
     #define GW_HWNDFIRST        0
     #define GW_HWNDNEXT         2
     #define GW_OWNER            4
     #define GW_CHILD            5
 
    PROCEDURE ATask
     LPARAMETER cMatriz, lOcultos

     *** Declaraciones de la funciones del API
     DECLARE ;
       Integer GetWindow ;
       IN WIN32API ;
       Integer  nHwnd, ;
       Integer  nCmd
     DECLARE ;
       Integer GetWindowText ;
       IN WIN32API ;
       Integer  nHwnd, ;
       String  @cString, ;
       Integer  nMaxCount
     DECLARE ;
       Integer GetWindowTextLength ;
       IN WIN32API ;
       Integer  nWnd
     DECLARE ;
       Integer IsWindowVisible ;
       IN WIN32API ;
       Integer  nWnd
     DECLARE ;
       Integer GetDesktopWindow ;
       IN WIN32API

     *** Declaración de variables
     PRIVATE nFoxHwnd, nCont, nCurrWnd
     PRIVATE nLength, cTmp
     RELEASE MEMORY &cMatriz
     PUBLIC (cMatriz)

     *** Obtención del handle del DeskTop
     nHwnd = GetDesktopWindow()
     nInitHwnd = GetWindow( nHwnd, GW_CHILD )

     *** Esta será la primera ventana
     nCurrWnd = GetWindow( nInitHwnd, ;
                           GW_HWNDFIRST )

     *** Inicializar contador
     nCont = 0

     *** Recorrer todas las ventanas
     DO WHILE nCurrWnd # 0

       *** Comprobar si no tiene padre
       IF GetWindow(nCurrWnd, GW_OWNER) = 0

         *** Si debemos las ventanas ocultas
         IF IsWindowVisible( nCurrWnd ) = 1 ;
            OR lOcultos

            *** Tamaño del título
           nLength=GetWindowTextLength(nCurrWnd)
           IF nLength > 0
             nCont = nCont + 1


             *** Obtener el título
             cTmp=REPLICATE( CHR(0), nLength+1 )
             =GetWindowText( nCurrWnd, ;
                             @cTmp, ;
                             nLength + 1 )

             *** Insertar un nuevo elemento
             DIMENSION &cMatriz.[nCont,2]
             &cMatriz.[nCont,1] = ;
               SUBSTR( cTmp, 1, nLength )
             &cMatriz.[nCont,2] = ;
               nCurrWnd
           ENDIF
         ENDIF
       ENDIF

       *** Obtener la siguiente ventana
       nCurrWnd = GetWindow( nCurrWnd, ;
                             GW_HWNDNEXT )
     ENDDO && (nCurrWnd # 0)

     *** Retornar el número de procesos
     RETURN nCont

Las función del API de Window que se utilizan este módulo que no hemos explicado en artículos anteriores son GetDesktopWindow() y IsWindowVisible().

GetDesktopWindow() obtiene el handle del Escritorio de Windows, lo que nos permitirá utilizar GetWindows() a partir de este handle.

IsWindowVisible() nos permite comprobar si una determinada ventana es visible o está oculta. Si pasamos como segundo parámetro al módulo aTask un valor verdadero (.T.) obtendremos también las ventanas ocultas. Probablemente nos sorprenderemos al ver las ventanas que andan ocultas por nuestro Windows.

 

Diferencias de aTask con FindWindow

Alguien observador podría preguntarse por que no usar directamente la función FindWindow() del API de Windows pasando el nombre de la ventana de principal de la aplicación con la que queremos interactuar en vez del método indirecto de utilizar aTask y luego buscar en la matriz resultante.

Podemos usar la función FindWindow() sin problemas, pero deberemos pasar exactamente el título de la ventana. Como muchas aplicaciones incluyen en el título el nombre del documento abierto, no es tan sencillo saber exactamente cual es el título de la ventana.

Personalmente prefiero utilizar una función propia como aTask, pues me permite hacer búsquedas aproximadas dentro de la matriz, y no necesito saber el título exacto de la ventana principal. Esta es una sencilla función para buscar una ventana con un título aproximado:

   PROCEDURE FindHwnd
     LPARAMETER cTitle

     *** Llama a aTask.PRG
     FOR nCont = 1 TO aTask( "aFindHwnd" )

       *** Busca dentro de la cadena
       IF UPPER( cTitle ) ;
          $ UPPER( aFindHwnd[nCont,1] )

         RELEASE MEMORY aFindHwnd
         RETURN aFindHwnd[nCont,2]
       ENDIF
     NEXT
     RELEASE MEMORY aFindHwnd
     RETURN 0

Cada uno deberá estudiar en que casos prefiere utilizar un método u otro.

 

Las Ventanas no son Procesos

Hemos llamado a la función anterior aTask, es decir, Array de Tareas, por similitud a la Lista de Tareas de Windows 3.x. Realmente lo que estamos haciendo es obtener los títulos de las ventanas principales que se están ejecutando en Windows. Esto no es exactamente lo mismo que las tareas. No debemos confundir las ventanas principales de las aplicaciones con las tareas (tasks) o los hilos (threads) de las aplicaciones. Muchas tareas de Win32 no disponen de ventanas asociadas.

Las funciones del API de Windows que estamos estudiando se dedican al manejo de ventanas y por eso nos hemos preocupado de las tareas como tales, pero en otras ocasiones nos puede interesar utilizar el identificador del proceso o ID de alguno de los threads de una aplicación. Este tipo de funciones están fuera del ámbito de estos artículos introductorios, pero son realmente interesantes.

Como curiosidad decir que Visual FoxPro tienen dos thread, uno para el interface y otro para el interpretador de comandos y el gestor de base de datos. Nosotros podemos crear un nuevo thread en Visual FoxPro, llamando al API de Windows, pero este thread no es utilizado por nuestro programa y no sirve para nada, por ello es mejor no hacerlo.

Si tiene interés en profundizar por este camino puede empezar por dos artículos publicados en la desaparecida Revista Microsoft de Programadores (RMP): ¿Cual es la diferencia entre ventanas y tareas en Windows 3.1? (Febrero de 1994) y Subiendo a 32 bits: Procesos, Theads y gestión de memoria en Chicago ( Diciembre de 1994). En el CD-Rom de Microsoft Developed Network dispone de la versión en inglés de estos artículos.

 

Arrancar otro programa

Todos hemos utilizado la orden RUN ó el símbolo ! de Visual FoxPro para arrancar otro programa. Personalmente prefiero usar RUN a !, pues este último puede confundirse con el operador lógico !, que es igual a un .NOT.. De todas las maneras es realmente muy sencillo.

En Windows tenemos la posibilidad de indicar una serie de operadores a esta orden a fin de controlar la forma de ejecutar la otra aplicación, por ejemplo el Bloc de Notas:

     *** Arranca y sigue la ejecución de FoxPro
     RUN /N notepad
     *** Arranca activo y a tamaño normal
     RUN /N1 notepad
     *** Arranca activo y a minimizado
     RUN /N2 notepad
     *** Arranca activo y a maximizado
     RUN /N3 notepad
     *** Arranca inactivo y a tamaño normal
     RUN /N4 notepad
     *** Arranca inactivo y a minimizado
     RUN /N7 notepad

En la mayoría de los casos basta con utilizar RUN ó ! con alguno de estos modificadores, pero si queremos arrancar una aplicación, por ejemplo, con su ventana principal oculta, debemos hacer uso de la función WinExec() del API de Windows. Esta función se declara como:

     DECLARE ;
       Integer WinExec ;
       IN WIN32API ;
       String   cCmdLine, ;
       Integer  nCmdShow

Como primer parámetro recibe la cadena que indica el programa a ejecutar, como segundo parámetro el modo en el que se desea visualizar esta aplicación. Los posibles modos de visualización son los mismos que describimos el mes pasado cuando hablamos de la función ShowWindows():

     #define SW_HIDE             0
     #define SW_SHOWNORMAL       1
     #define SW_NORMAL           1
     #define SW_SHOWMINIMIZED    2
     #define SW_SHOWMAXIMIZED    3
     #define SW_MAXIMIZE         3
     #define SW_SHOWNOACTIVATE   4
     #define SW_SHOW             5
     #define SW_MINIMIZE         6
     #define SW_SHOWMINNOACTIVE  7
     #define SW_SHOWNA           8
     #define SW_RESTORE          9
     #define SW_SHOWDEFAULT      10
     #define SW_MAX              10

La función devuelve un número por encima de 32 si ha tenido éxito, si existe algún problema para arrancar esta aplicación, devuelve un código de error por debajo de este número 32.

Para arrancar el Bloc de Notas de forma oculta deberías ejecutar algo como esto:

     =WinExec( "Notepad", SW_HIDE )

Después de esto no vemos la aplicación arrancada, pero si ejecutamos la función aTask con el segundo parámetro a .T., tal y como describíamos al principio de este artículo, veremos que si se ha arrancado la aplicación y que podemos hacerla visible.

Algunas aplicaciones no podremos hacerlas invisibles, pues entre su código de arranque tienen una modificación del estado de su ventana, por ejemplo a maximizado, y esto provoca que se hagan siempre visibles. Deberemos probar la aplicación que queremos arrancar de forma oculta.

Para hacer visible la aplicación utilizaremos la función ShowWindow() del API de Windows, pasando el handle de la ventana y como segundo parámetro alguno de los distintos modos de visualización o simplemente SW_SHOW.

Si no hacemos visible la otra aplicación o no la cerramos desde nuestro código, la aplicación que hemos arrancado quedará de forma permanente, consumiendo recursos, pues el usuario no tiene medio de cancelar esta ejecución. Por ello debemos ser muy cuidadosos a la hora de aplicar esta técnica.

 

Primera aproximación a los mensajes: Cerrar otra aplicación

Cualquier actuación con otra aplicación debe realizarse con sumo cuidado. Tal y como decíamos en el artículo anterior: aunque se puedan hacer determinadas cosas por medio del API no es muy correcto hacerlas sin más. Debemos, por lo tanto, ser respetuosos con las demás aplicaciones que puedan estar corriendo en Windows.

En este caso, si arrancamos otra aplicación de forma invisible nosotros somos responsable de cerrarla. Para cerrar otra aplicación podemos utilizar una nueva técnica, enviar un mensaje.

Windows se basa, en gran medida, en el paso de mensajes desde Windows a las aplicación, incluso entre las aplicaciones entre si.

Todas ventanas de Windows tienen asociadas un programa que gestiona los mensajes de esa ventana. Los mensajes para un programador de Visual FoxPro se puede asemejar a los eventos, es decir, cuando, por ejemplo, se mueve el ratón, Windows envía un mensaje indicando que se ha realizado este movimiento y Visual FoxPro ejecuta el código asociado a este evento.

Los mensajes pueden ser de muchos tipos. Basta ver la documentación del API de Windows y observar que el inmenso número de mensajes que se pueden enviar o recibir. Los mensajes los pueden producir el usuarios, Windows (por ejemplo el mensaje de un Timer) u otras aplicaciones.

Por ejemplo, para cerrar una ventana, podemos enviar un mensaje WM_CLOSE. Si esta ventana es la ventana principal de una aplicación, se cierra esta aplicación. Para enviar este mensaje a otra aplicación en Win32 debemos usar la función PostMessage(). Veamos un pequeño ejemplo:

     #DEFINE WM_CLOSE  16
     DECLARE ;
       Integer PostMessage ;
       IN WIN32API ;
       Integer  nWnd, ;
       Integer  nMsg, ;
       Integer  nParam, ;
       Integer  nParam

     *** Arranca en NotePad
     WAIT WINDOW ;
       "Se va a arrancar el Bloc, " + ;
       "escriba algo en él y espere..." ;
       TIMEOUT 3
     RUN /N NotePad

     *** Cierra preguntando si ha habido cambios
       WAIT WINDOW ;
       "El Block se va ha cerrar..." ;
       TIMEOUT 5

     *** La función FindHwnd es un PRG
     nHwndNote = FindHwnd( "Bloc de notas" )
     =PostMessage( nHwndNote, WM_CLOSE, 0, 0 )

La función PostMessage() necesita el handle de la ventana a la que enviamos el mensaje, en código de mensaje, y dos parámetros necesarios para pasar variantes a algunos mensajes.

El mensaje WM_CLOSE provoca que la aplicación se cierre, pero muchas aplicaciones, como el Bloc de Notas, preguntan si deseamos guardar el documento abierto antes de cerrar. Si necesitamos cancelar una aplicación de forma rotunda, podemos utilizar otro mensaje, el WM_QUIT, que corresponde al número 18. No debemos usar sin más este último método, pues en algunas aplicaciones puede producir la corrupción de datos y debemos ser muy, muy, cuidadosos con esta posibilidad.

No vamos a describir aquí los mensajes que podemos utilizar. Sirvan estos dos como botón de muestra. Si consulta la documentación sobre el API de Windows podrá descubrir todas las posibilidades del envío de mensajes.

 

Los mensajes en VFP

Normalmente utilizaremos los mensajes que reciben las ventanas de Visual FoxPro por medio de los eventos de las ventanas. Recordemos que, normalmente los mensajes de Windows corresponden a un evento de Visual FoxPro. Así, por ejemplo, el mensaje WM_MOUSEMOVE corresponde con el evento MouseMove de Visual FoxPro. Esto no siempre es tan sencillo. No todos los mensajes de Windows corresponden con un evento de Visual FoxPro, sobre todo los mensajes menos habituales.

No debemos preocuparnos por esta limitación, pero si necesitamos utilizar alguno de estos mensajes no reflejados directamente en VFP o los mensajes enviados por otras aplicaciones, como por ejemplo los enviados por Windows Socket, deberemos usar alguna librería u OCX que nos permita manejarlos.

Por ejemplo, el control OCX de comunicaciones que viene con Visual FoxPro (Microsoft Comm Control) enlaza los mensajes producidos en una comunicación asíncrona con una serie de eventos de este control y manejables por Visual FoxPro.

Si recuerda lo que decíamos en el artículo anterior, todo en Windows en una ventana, un Botón o un Check Box son ventana y por lo tanto si conseguimos en handle de un control, podemos enviar un mensaje a ese control.

En Visual FoxPro esto no es totalmente cierto, pues gran parte de los controles de Visual FoxPro son controles especiales y sólo son ventanas cuando tienen el foco. Si obtenemos el handle de la ventana de este control cuando tiene el foco, podemos enviar algunos mensajes a la misma.

 

Conclusión

Debemos repetir, una vez más, que no todo lo que se puede hacer con el API de Windows esta bien hecho. Por lo tanto, sean respetuosos con las demás aplicaciones que puedan estar corriendo en Windows y utilicen lo aquí expuesto con cuidado.

No hemos hecho más que esbozar brevemente algunas de las posibilidades del manejo de ventanas. Le invito a seguir estudiando por su cuenta las funciones y mensajes que nos ofrece para el manejo de ventanas. El próximo artículo cambiaremos te tema y lo dedicaremos a las funciones para el manejo del Registro de Win32.

Si no puede esperar y desea avanzar en el conocimiento del API de Windows puede ver el fichero de ayuda sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien la magnífica información y ejemplos contenidos en los CD-ROM del Microsoft Developer Network (MSDN) o los manuales de Visual C++ 4.x.

 


 

Vea también

Uso del API de Windows: Índice general

Tema principal API

 

Descarga

Uso del API de Windows: Introducción a las Ventanas (V)

 


 

 

 



error: Contenido protegido