Capítulo 32 Objetos básicos del usuario: El cursor

El cursor o puntero, indica la posición del ratón en pantalla, y nos permite acceder a los elementos de las ventanas, al tiempo que su aspecto nos da pistas sobre la acción asociada al cursor, o sobre el estado del sistema.

Es un recurso importante y único en el sistema, por lo que hay que compartirlo entre las diferentes aplicaciones. Algunos de los cambios que hagamos en el cursor se deben deshacer cuando el control pase a otras aplicaciones.

Cursor de clase

Uno de los parámetros que asignamos al registrar una clase de ventana, usando la estructura WNDCLASS o WNDCLASSEX y las funciones RegisterClass o RegisterClassEx es, precisamente, el cursor de la clase. Windows siempre muestra ese cursor mientras esté dentro del área de cliente de la ventana.

    WNDCLASSEX wincl;        /* Estructura de datos para la clase de ventana */

    /* Estructura de la ventana */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = "NUESTRA_CLASE";
    wincl.lpfnWndProc = WindowProcedure;      /* Esta función es invocada por Windows */
    wincl.style = CS_DBLCLKS;                 /* Captura los doble-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Usar icono y puntero por defector */
    wincl.hIcon = LoadIcon (hThisInstance, "Icono");
    wincl.hIconSm = LoadIcon (hThisInstance, "Icono");
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = "Menu";
    wincl.cbClsExtra = 0;                      /* Sin información adicional para la */
    wincl.cbWndExtra = 0;                      /* clase o la ventana */
    /* Usar el color de fondo por defecto para es escritorio */
    wincl.hbrBackground =GetSysColorBrush(COLOR_BACKGROUND);

    /* Registrar la clase de ventana, si falla, salir del programa */
    if(!RegisterClassEx(&wincl)) return 0;

Hasta ahora, en todos nuestros ejemplos cargábamos el cursor de la flecha, pero en este capítulo veremos que podemos usar nuestro propio cursor de clase para nuestras ventanas, bastará con asignar otro cursor al miembro hCursor. Veremos a continuación qué cursores podemos usar.

Cursores de recursos

Se pueden incluir cursores diseñados por nosotros o almacenados en ficheros ".cur" en el fichero de recursos mediante la sentencia CURSOR, y obtener un manipulador para ellos usando las funciones LoadCursor o LoadImage.

Flecha3D CURSOR "3dgarro.cur"

Para usar estos cursores en nuestra aplicación usaremos la función LoadCursor, indicando la instancia actual y el identificador de recurso:

   HCURSOR flecha3d = LoadCursor(hInstance, "flecha3D");

O bien la funcón LoadImage:

HCURSOR flecha3d = LoadImage(hInstance, "flecha3D", IMAGE_CURSOR, 0, 0, LR_LOADREALSIZE);

Cursores estándar

Podríamos llamarlos cursores de stock, aunque en realidad no lo son, ya que los cursores estándar se pueden personalizar por el usuario al cambiar las opciones del escritorio.

Existen los siguientes cursores estándar:

cursores estándar
Valor Descripción
IDC_APPSTARTING Flecha estándar y un pequeño reloj de arena.
IDC_ARROW Flecha estándar.
IDC_CROSS Cruz.
IDC_IBEAM I para texto.
IDC_ICON Sólo en Windows NT: icono vacío
IDC_NO Círculo barrado.
IDC_SIZE Sólo en Windows NT: flecha de cuatro puntas.
IDC_SIZEALL Igual que IDC_SIZE.
IDC_SIZENESW Flecha de dos puntas noreste y sudoeste.
IDC_SIZENS Flecha de dos puntas, norte y sur.
IDC_SIZENWSE Flecha de dos puntas, noroeste y sudeste.
IDC_SIZEWE Flecha de dos puntas, este y oeste.
IDC_UPARROW Flecha vertical.
IDC_WAIT Reloj de arena.

El aspecto gráfico de cada uno depende de la configuración del escritorio de Windows, y se puede modificar usando el Panel de Control. En nuestra aplicación podemos cargar cualquiera de ellos usando la función LoadCursor, indicando NULL como manipulador de instancia, y el identificador que queramos.

   HCURSOR cursor = LoadCursor(NULL, IDC_ARROW);
   DrawIconEx(hdc, 10,120, cursor, 0, 0, 0, NULL, DI_NORMAL|DI_COMPAT);

También podemos cambiar esos cursores mediante la función del API SetSystemCursor. El primer parámetro es un manipulador de cursor, y los valores adecuados para el segundo parámetro son los mismos que en la tabla anterior, pero con el prefijo "OCR_", en lugar de "IDC_":

   HCURSOR flecha3d = LoadCursor(hInstance, "flecha3D");
   SetSystemCursor(flecha3d, OCR_NORMAL);

Estos cambios son permanentes, en el sentido de que no se restituyen al abandorar la aplicación, y para recuperar los cursores previos hay que usar la misma función o bien el panel de control.

Similitud entre iconos y cursores

Existe cierta similitud entre cursores e iconos, el formato en el que se guardan es similar, y se puede usar la función DrawIconEx para mostrar un icono, tanto como un cursor. Del mismo modo que se puede usar GetIconInfo para obtener información sobre un cursor.

   hdc = BeginPaint(hwnd, &ps);
   DrawIconEx(hdc, 10,50, caballo, 0, 0, 0, NULL, DI_NORMAL|DI_COMPAT);
   EndPaint(hwnd, &ps);

Además, es posible usar un icono como cursor, aunque con algunas limitaciones, dependiendo del hardware instalado.

   HICON tajmahal = LoadImage(hInstance, "tajmahal", IMAGE_ICON, 0, 0, LR_LOADREALSIZE);
   SetClassLong(hwnd, GCL_HCURSOR, (LONG)tajmahal);

Los cursores pueden ser monocromo, en color, o animados. Aunque algunos sistemas sólo admiten determinados tipos de cursores, monocromo y de determinadas dimensiones, sobre todo antes de windows 95. Por ejemplo, no se pueden usar cursores en color con una pantalla VGA.

El punto activo (Hot Spot)

La similitud entre iconos y cursores se da también en la propiedad del punto activo. En el caso del icono sólo se usaba para alinear o situar el icono en pantalla, en el caso de cursor su función es diferente, el punto activo indica el punto exacto de la acción del ratón, es el punto que se considera como la posición del cursor.

Los mensajes del ratón suelen referirse a un punto concreto, y ese punto es el punto activo del cursor.

Crear cursores

Los cursores estándar no es necesario crearlos, ya que existen como parte del sistema. Podemos usar las funciones LoadCursor o LoadImage para obtener manipuladores de esos cursores. Las mismas funciones se usan para obtener manipuladores de cursores de recursos.

En el caso de cursores animados no es posible crearlos a partir de recursos, de modo que hay que cargarlos directamente desde un fichero ".ani" durante la ejecución, usando la función LoadCursorFromFile, esta función también puede cargar ficheros de cursores no animados, con extensión ".cur".

   HCURSOR caballo = LoadCursorFromFile("horse.ani");

También se pueden crear de forma dinámica mediante CreateIconIndirect, y una estructura ICONINFO, GetIconInfo, pero es preferible usar cursores de recursos, ya que se evitan problemas derivados de la dependencia de dispositivo.

Posición del cursor

La posición del cursor cambia como reflejo del movimiento del ratón, pero podemos cambiar su posición en cualquier momento usando la función SetCursorPos. También, podemos recuperar la posición actual del cursor mediante GetCursorPos.

Estas dos funciones trabajan con coordenadas de pantalla. Si queremos obtener o usar coordenadas de ventana para leer o modificar la posición del cursor podemos usar las funciones ScreenToClient o ClientToScreen.

   punto.x = 60;
   punto.y = 60;
   ClientToScreen(hwnd, &punto);
   SetCursorPos(punto.x, punto.y);
   GetCursorPos(&punto);
   ScreenToClient(hwnd, &punto);

Apariencia

Para obtener un manipulador del cursor actual se usa la función GetCursor.

Para cambiar el cursor actual se usa la función SetCursor, y sólo después de mover el cursor se mostrará la nueva apariencia. Recordemos que el sistema se encarga de mostrar siempre el cursor de acuerdo para la zona sobre la que esté, y cuando se sitúa en el área de cliente, se usa el cursor de la clase.

De modo que para que sea posible cambiar la apariencia del cursor, el cursor de la clase debe ser NULL. Pero esto significa que si movemos el cursor fuera del área de cliente, el cursor cambia automáticamente, y al regresar al área de cliente, mantiene el aspecto que tenía después de la última asignación de cursor. Por ejemplo, si situamos el cursor sobre el borde derecho se mostrará el cursor de doble flecha, este-oeste. Si volvemos al área de cliente, se mantiene ese cursor.

SetCursor(LoadCursor(hInstance, "flecha3D"));

Hay dos modos de evitar esto, uno es modificar el cursor de la clase, el otro, procesar el mensaje WM_SETCURSOR.

Modificar el cursor de clase

Ya vimos al principio del capítulo que es posible asignar un cursor para la clase de ventana, y que ese cursor se usará en el área de cliente de todas las ventanas de esa clase. Ahora bien, es posible cambiar el cursor asociado a una clase, y en consecuencia, para todas las ventanas de dicha clase, esto se hace mediante la función SetClassLong.

Esta función puede, en principio, cambiar cualquier valor de la estructura WNDCLASS o WNDCLASSEX, pero en este caso nos limitaremos al cursor. Es tan sencillo como llamarla, usando como parámetros el manipulador de ventana, el índice correspondiente al cursor, que es GCL_HCURSOR, y el manipulador del nuevo cursor de clase, convertido a un valor LONG:

   SetClassLong(hwnd, GCL_HCURSOR,
      (LONG)LoadCursorFromFile("horse.ani"));

El mensaje WM_SETCURSOR

El mensaje WM_SETCURSOR nos permite un control mucho mayor sobre el aspecto del cursor, incluso aunque éste abandone el área de cliente. Se recibe cada vez que el cursor se mueve dentro de la ventana, y de ese modo podemos cambiar la apariencia del cursor a capricho.

Podemos, por ejemplo, cambiar el cursor dependiendo de la zona de la ventana, verificando si se encuentra dentro de un rectángulo o de una región determinada.

Si el cursor está fuera de cualquiera de las zonas de nuestro interés, probablemente queramos que su aspecto sea el esperado, por ejemplo, cuando esté sobre un borde o sobre la barra de menús. En este caso, debemos dejar que el mensaje se procese por el procedimiento por defecto:

        case WM_SETCURSOR:
           SetRect(&re, 20, 20, 80,80);
           GetCursorPos(&punto);
           ScreenToClient(hwnd, &punto);
           if(PtInRect(&re, punto))
              SetCursor(LoadCursorFromFile("horse.ani"));
           else
              return DefWindowProc(hwnd, msg, wParam, lParam);
           break;

En este ejemplo, si el cursor está sobre el rectángulo de coordenadas (20,20)-(80,80) se mostrará el cursor del caballo, en caso contrario, se ejecuta el proceso del mensaje por defecto, es decir: si el cursor está sobre el área de cliente, se mostrará el cursor de la clase, y en el área de no cliente, se mostrará el cursor que corresponda.

El problema con los cursores animados es que cada vez que se recibe el mensaje WM_SETCURSOR se vuelve a asignar el cursor, y éste vuelve a la primera imagen de la animación. Deberíamos crear un procedimiento que sólo lo cambie la primera vez que entramos en el área especificada, y no cada vez que se procese el mensaje, esto no sucede si cambiamos el cursor de clase.

Ocultar y mostrar

También podemos ocultar o mostrar el cursor en cualquier momento, para ello usaremos la función ShowCursor. Esta función admite un parámetro de tipo BOOL, si es TRUE, el valor del contador se incrementa, si es FALSE, se decrementa. Si el valor del contador es mayor o igual que cero, el cursor se muestra, en caso contrario, se oculta.

La utilidad de ocultar el cursor es limitada, tal vez, evitar que el usuario lleve a cabo ciertas tareas que impliquen el uso de ratón en determinados momentos.

Confinar el cursor

En ocasiones nos puede interesar que el cursor no salga de cierta zona de la pantalla hasta que se se cumplan ciertas condiciones especiales, por ejemplo, podemos confinar el cursor a una zona concreta de una ventana, y limitar de este modo las posibilidades de mover el cursor, o de hacer clic en ciertas partes de la pantalla.

Para esto disponemos de la función ClipCursor, que admite un parámetro de tipo RECT que define el rectángulo del que el cursor no puede salir. La función GetClipCursor permite recuperar ese rectángulo.

El cursor está asociado al ratón, y ambos son recursos únicos en el sistema, es decir, una aplicación no debería adueñarse de ellos de forma exclusiva. Cuando una aplicación que ha confinado el cursor pierde el foco, debe liberarlo para que pueda acceder a cualquier punto de la pantalla.

Esto se puede hacer procesando los mensajes WM_SETFOCUS y WM_KILLFOCUS:

        case WM_SETFOCUS:
           ClipCursor(NULL);
           break;
        case WM_SETFOCUS:
           GetWindowRect(hwnd, &re);
           ClipCursor(&re);
           break;

Este ejemplo confina el cursor a la ventana de la aplicación, e impide que el cursor se use fuera de ella. Si la aplicación pierde el foco, el cursor se libera, y cuando lo recupera, se vuelve a confinar.

Pero no bastará con esto, ya que cada vez que la ventana se mueva o cambie de tamaño, el cursor vuelve a quedar libre. Para evitar que esto suceda podemos recurrir a dos nuevos mensajes: WM_SIZE y WM_MOVE, que se reciben cuando la ventana cambia de tamaño o de posición, respectivamente.

Sencillamente, procesaremos los mensajes WM_SETFOCUS, WM_SIZE y WM_MOVE del mismo modo:

        case WM_SETFOCUS:
           ClipCursor(NULL);
           break;
        case WM_MOVE:
        case WM_SIZE:
        case WM_SETFOCUS:
           GetWindowRect(hwnd, &re);
           ClipCursor(&re);
           break;

Destrucción de cursores

Cursores creados con CreateIconIndirect se destruyen con DestroyCursor, no es necesario destruir el resto de los cursores.

Ejemplo 33


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 33 win033.zip 2004-06-28 15042 bytes 355