Arrastre de imágenes

Las operaciones de arrastrar y soltar siguen siempre un patrón parecido. Empiezan con una pulsación de ratón sobre el objeto a arrastrar. Mientras se mantiene pulsado, el objeto seleccionado se mueve junto con el cursor del ratón. Cuando se suelta el botón del ratón, el objeto se suelta en el punto actual del cursor.

Generalmente usaremos el botón izquierdo del ratón, eso quiere decir que deberemos procesar tres mensajes: WM_LBUTTONDOWN, WM_MOUSEMOVE y WM_LBUTTONUP.

Inicio del arrastre

Cuando detectemos la pulsación del botón del ratón, comprobaremos si el cursor está sobre el objeto a arrastrar. Este proceso puede ser muy complejo si los posibles objetos a arrastrar son muchos. La técnica común es crear un rectángulo o una región para cada objeto, y comprobar si las coordenadas del cursor están en cada uno de esos rectángulos o regiones, usando las funciones PtInRect o PtInRegion.

Una vez localizado el objeto seleccionado entramos en el modo de arrastre. Esto implica algunas acciones, como capturar el ratón para la ventana actual, usando SetCapture, opcionalmente, ocultar el cursor con ShowCursor, ya que el objeto arrastrado puede hacer su función, y borrar el objeto de su posición inicial, ya que ahora deberá desplazarse sobre la ventana a la vez que el ratón. También necesitamos una variable estática que nos indique que estamos en una operación de arrastrar y soltar.

Finalmente tenemos que usar dos funciones específicas cuando se arrastran imágenes pertenecientes a listas de imágenes. ImageList_BeginDrag y ImageList_DragEnter.

La primera función, ImageList_BeginDrag, crea una lista de imágenes temporal que se usa para mostrar la imagen a arrastrar. Necesita cuatro parámetros: el primero es un manipulador de la lista de imágenes que contiene la imagen a arrastrar, el segundo el índice dentro de la lista que identifica la imagen. El tercero y cuarto indican las coordendas del punto caliente. Este punto es análogo al punto activo de un cursor. De hecho, la imagen arrastrada se comporta de forma similar a cómo lo hace un cursor del ratón. Las coordenadas del punto de acceso son relativas a la esquina superior izquierda de la imagen, por lo que tendremos que hacer ciertos cálculos a partir de las coordenadas del ratón y las de la esquina superior izquierda de la imagen, concretamente, esos cálculos son una resta.

La segunda función, ImageList_DragEnter, se encarga de bloquear las actualizaciones de la ventana especificada durante una operación de arrastre y además muestra la imagen arrastrada en la posición especificada dentro de la ventana. Esta función requiere tres parámetros: el primero es un manipulador de la ventana en la que se va a realizar la operación de arrastre, el segundo y tercer parámetros son las coordenadas en las que se muestra la imagen, pero hay que tener cuidado, ya que esas coordenadas son relativas a la esquina superior izquierda de la ventana, y no del área de cliente. Necesitaremos, pues, el desplazamiento del área de cliente con respecto a la ventana. Podemos hacerlo mediante estas llamadas:

    static int cxBorde;
    static int cyBorde;
    RECT rv;
    POINT p;
...
           GetWindowRect(hwnd, &rv);
           p.x=p.y=0;
           ClientToScreen(hwnd, &p);
           cxBorde = p.x-rv.left;
           cyBorde = p.y-rv.top;

Primero obtenemos las coordenadas de la ventana en el rectángulo rv. El rectángulo contendrá las equinas de la ventana en coordenadas de pantalla. Iniciamos el punto p con las coordenadas 0,0, y traducimos esas coordenadas de cliente a pantalla. El desplazamiento del área de cliente sobre la esquina superior izquierda de la ventana es la diferencia entre las coordenadas del punto y las de la esquina superior izquierda en rv.

El tratamiento del mensaje WM_LBUTTONDOWN queda así:

        case WM_LBUTTONDOWN:
           ptCur.x = LOWORD(lParam);
           ptCur.y = HIWORD(lParam);
           if(!PtInRect(&re, ptCur)) return FALSE;
           // Capturar el ratón
           SetCapture(hwnd);
           endrag = TRUE;
           ShowCursor(FALSE);
           // Borrar la imagen a arrastrar:
           InvalidateRect(hwnd, &re, TRUE);
           UpdateWindow(hwnd);
           // Calcular el hotspot:
           hotspot.x = ptCur.x-re.left;
           hotspot.y = ptCur.y-re.top;
           ImageList_BeginDrag(hIml, 2, hotspot.x, hotspot.y);
           ImageList_DragEnter(hwnd, ptCur.x + cxBorde, ptCur.y + cyBorde);
           break;

Arrastre

Durante el arrastre mantendremos pulsado el botón del ratón, y procesaremos el mensaje WM_MOUSEMOVE. Cada vez que lo procesemos invocaremos a la función ImageList_DragMove, al que pasaremos las coordenadas de ventana de la posición de la imagen. De nuevo, las coordenadas del ratón que obtenemos del mensaje son coordenadas de cliente, por lo que habrá que añadir el desplazamiento del área de cliente.

        case WM_MOUSEMOVE:
           if(!endrag) return TRUE;
           ptCur.x = LOWORD(lParam);
           ptCur.y = HIWORD(lParam);
           ImageList_DragMove(ptCur.x + cxBorde, ptCur.y + cyBorde);
           break;

Final del arrastre

Finalmente, en algún momento finalizaremos la operación de arrastrar y soltar, soltando el botón del ratón. Para ello procesaremos el mensaje WM_LBUTTONUP.

Lo primero es terminar la operación de arrastre, invocando a la función ImageList_EndDrag, que no necesita argumentos. A continuación invocaremos a la función ImageList_DragLeave, usando como argumento un manipulador de la ventana. Esta función vuelve a permitir las actualizaciones en la ventana, borra la imagen arrastrada, y también la lista de imágenes temporal creada para la operación de arrastre.

Sólo nos queda volver a hacer visible el cursor, si lo habíamos ocultado, dibujaremos la imagen en la posición final, y liberamos el ratón.

Finalmente, actualizamos el rectángulo con la nueva posición de la imagen, de modo que podamos volver a arrastrarla.

        case WM_LBUTTONUP:
           if(!endrag) return TRUE;
           ptCur.x = LOWORD(lParam);
           ptCur.y = HIWORD(lParam);
           ImageList_EndDrag();
           ImageList_DragLeave(hwnd);

           ShowCursor(TRUE);
           endrag = FALSE;

           DibujarImagen(hwnd, hIml, 2, ptCur.x - hotspot.x, ptCur.y - hotspot.y);

           ReleaseCapture();
           re.left = ptCur.x - hotspot.x;
           re.top = ptCur.y - hotspot.y;
           re.right = re.left+32;
           re.bottom = re.top+32;
           return TRUE;

Hay otras dos funciones relacionadas con el arrastre de imágenes. ImageList_SetDragCursorImage crea una nueva imagen de arrastre mediante la combinación de la imagen especificada, generalmente la imagen de un cursor del ratón, y de la imagen actualmente arrastrada. Si se usa esta imagen generada es imprescindible ocultar el cursor, ya que de otro modo se mostrarán dos cursores, lo que siempre es poco conveniente.

Ya hemos mencionado que ImageList_BeginDrag crea una lista de imágenes temporal, la función ImageList_GetDragImage recupera la imagen temporal, además de su posición actual y el desplazamento del punto activo.

Ejemplo 82


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 82 win082.zip 2012-06-15 15651 bytes 53

Información de imagen

Disponemos de dos funciones para obtener información sobre listas de imágenes. La primera es ImageList_GetImageInfo, que obtiene algunos datos sobre una imagen dentro de una lista de imágenes. Se necesitan tres parámetros, el primero es un manipulador de una lista de imágenes, el segundo el índice de la imagen sobre la que queremos información, y el tercero un puntero a una estructura IMAGEINFO en la que se almacenarán los datos sobre la imagen. Esta estructura tiene cinco campos, aunque dos de ellos no se usan:

  • hbmImage: contiene un manipulador del mapa de bits que contiene las imágenes de la lista de imágenes.
  • hbmMask: contiene un manipulador del mapa de bits monocromo que contiene la máscara de las imágenes de la lista de imágenes.
  • rcImage: rectángulo que bordea la imagen indicada en el índice dentro del mapa de bits.

La otra función es ImageList_GetImageCount que devuelve el número de imágenes que contiene la lista de imágenes cuyo manipulador se ha pasado como parámetro.