Uso de cursores

En esta sección se describen los temas siguientes.

Creación de un cursor

El siguiente ejemplo crea dos identificadores de cursor: uno para el cursor estándar en forma de reloj de arena y otro para un cursor personalizado incluido como recurso en el archivo de definición de recursos de la aplicación.

HCURSOR hCurs1 = LoadCursor(NULL, IDC_WAIT);
HCURSOR hCurs2 = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYICON));

Las aplicaciones deben implementar cursores personalizados como recursos y usar LoadCursor, LoadCursorFromFile o LoadImage en lugar de crear el cursor en tiempo de ejecución. El uso de recursos de cursor evita la dependencia del dispositivo, simplifica la localización y permite a las aplicaciones compartir diseños de cursor.

En el ejemplo siguiente se usa la función CreateCursor para crear un cursor monocromo personalizado en tiempo de ejecución. Aquí se incluye el ejemplo para ilustrar cómo interpreta el sistema las máscaras de cursor.

Cada píxel del mapa del cursor se representa mediante un solo carácter:

Símbolo Bit AND Bit XOR Pantalla
1 0 Transparent
X 1 1 Invertir pantalla
o 0 1 Blanco
+ 0 0 Negro
#define CURSOR_SIZE 32

// Symbol encoding: ' '=transparent  'o'=white  '+'=black
static const char *const yin_cursor[CURSOR_SIZE] = {
    "              ++++              ",
    "          ++++oooo+             ",
    "        ++oooooo++              ",
    "       +ooooooo+                ",
    "     +oooooooo+                 ",
    "    +oooooooo+                  ",
    "    +oooooooo+                  ",
    "   +oooooooo+                   ",
    "  +ooooooooo+                   ",
    "  +oooooooo+                    ",
    " +ooooooooo+                    ",
    " +ooooooooo+                    ",
    " +oooooooooo+                   ",
    "+ooooooooooo+                   ",
    "+oooooooooooo+                  ",
    "+ooooooooooooo++                ",
    "+ooooooooooooooo+               ",
    "+oooooooooooooooo++             ",
    "+oooooooooooooooooo+            ",
    " +ooooooooooooooooo+            ",
    " +oooooooo+++ooooooo+           ",
    " +ooooooo+++++oooooo+           ",
    "  +oooooo+++++oooooo+           ",
    "  +oooooo+++++ooooo+            ",
    "   +oooooo+++oooooo+            ",
    "    +ooooooooooooo+             ",
    "    +ooooooooooooo+             ",
    "     ++oooooooooo+              ",
    "       +oooooooo+               ",
    "        ++oooo++                ",
    "          ++++                  ",
    "                                ",
};

// Pack an XPM-style cursor map into separate 1bpp AND and XOR bit planes.
// Output buffers must be sized: ((w + 15) / 16) * 2 * h bytes.
static void PackCursorMasks(const char *const rows[], int w, int h,
                             BYTE *pbAnd, BYTE *pbXor)
{
    int stride = ((w + 15) / 16) * 2;  // WORD-aligned row stride, per CreateCursor contract
    ZeroMemory(pbAnd, stride * h);
    ZeroMemory(pbXor, stride * h);
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            BYTE bit = (BYTE)(0x80 >> (x % 8));
            int  idx = y * stride + x / 8;
            char sym = rows[y][x];
            if (sym == ' ' || sym == 'X') pbAnd[idx] |= bit;
            if (sym == 'o' || sym == 'X') pbXor[idx] |= bit;
        }
    }
}

// Row stride for a 32-pixel-wide 1bpp mask: ((32 + 15) / 16) * 2 = 4 bytes.
BYTE abAnd[CURSOR_SIZE * 4];
BYTE abXor[CURSOR_SIZE * 4];
PackCursorMasks(yin_cursor, CURSOR_SIZE, CURSOR_SIZE, abAnd, abXor);

// hInstance is the application's HINSTANCE from WinMain.
// Call DestroyCursor when the cursor is no longer needed.
HCURSOR hCurs3 = CreateCursor(
    hInstance,  // application instance
    19,         // hot spot x
    2,          // hot spot y
    CURSOR_SIZE,
    CURSOR_SIZE,
    abAnd,
    abXor);

Para obtener más información, consulte Mapas de bits.

Uso de funciones de cursor para crear una trampa para ratones

En el ejemplo siguiente se usan las funciones SetCursorPos, GetCursorPos, CreateCursor, CreateIcon, SetCursor y DrawIconEx para crear una ratonera sencilla. Se dibuja un icono con forma yang en el centro de la ventana. Si el cursor no se ha movido durante 3 segundos, se ajusta al icono yang y cambia a la forma yin. Al mover el ratón, se restablece el bloqueo.

// PackCursorMasks, yin_cursor, and CURSOR_SIZE are defined in the preceding example.

#define ICON_SIZE     32
#define YIN_HOT_X     19
#define YIN_HOT_Y      2
#define IDT_CURSOR     1
#define TRAP_DELAY  3000    // ms of inactivity before the cursor snaps to the icon

// Symbol encoding: ' '=transparent  'o'=white  '+'=black
static const char *const yang_icon[ICON_SIZE] = {
    "                                ",
    "                  ++++          ",
    "                ++++++++        ",
    "               ++++++++++       ",
    "              +++++++++++++     ",
    "             +++++++++++++++    ",
    "             +++++++++++++++    ",
    "            ++++++ooo++++++++   ",
    "            +++++ooooo++++++++  ",
    "           ++++++ooooo++++++++  ",
    "           ++++++ooooo+++++++++ ",
    "           +++++++ooo++++++++++ ",
    "            +++++++++++++++++++ ",
    "            ++++++++++++++++++++",
    "             +++++++++++++++++++",
    "              ++++++++++++++++++",
    "                ++++++++++++++++",
    "                 +++++++++++++++",
    "                   +++++++++++++",
    "                   ++++++++++++ ",
    "                    +++++++++++ ",
    "                    +++++++++++ ",
    "                    ++++++++++  ",
    "                   +++++++++++  ",
    "                   ++++++++++   ",
    "                  ++++++++++    ",
    "                  ++++++++++    ",
    "                 ++++++++++     ",
    "                +++++++++       ",
    "              ++++++++++        ",
    "             +++++++++          ",
    "              ++++              ",
};

static HICON   hYang;
static HCURSOR hYin;
static POINT   ptLast;
static BOOL    bTrapped;

LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CREATE:
        {
            HINSTANCE hInstance = ((LPCREATESTRUCT)lParam)->hInstance;

            BYTE abAndIcon[ICON_SIZE * 4];
            BYTE abXorIcon[ICON_SIZE * 4];
            PackCursorMasks(yang_icon, ICON_SIZE, ICON_SIZE, abAndIcon, abXorIcon);
            hYang = CreateIcon(hInstance, ICON_SIZE, ICON_SIZE, 1, 1,
                               abAndIcon, abXorIcon);

            BYTE abAndCursor[CURSOR_SIZE * 4];
            BYTE abXorCursor[CURSOR_SIZE * 4];
            PackCursorMasks(yin_cursor, CURSOR_SIZE, CURSOR_SIZE, abAndCursor, abXorCursor);
            hYin = CreateCursor(hInstance, YIN_HOT_X, YIN_HOT_Y, CURSOR_SIZE, CURSOR_SIZE,
                                abAndCursor, abXorCursor);

            GetCursorPos(&ptLast);
            SetTimer(hWnd, IDT_CURSOR, TRAP_DELAY, (TIMERPROC)NULL);
            return 0;
        }

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint(hWnd, &ps);
            RECT rc;
            GetClientRect(hWnd, &rc);
            DrawIconEx(hDC,
                       (rc.right  - ICON_SIZE) / 2,
                       (rc.bottom - ICON_SIZE) / 2,
                       hYang, ICON_SIZE, ICON_SIZE, 0, NULL, DI_NORMAL);
            EndPaint(hWnd, &ps);
            return 0;
        }

        case WM_MOUSEMOVE:
        {
            POINT ptNow;
            GetCursorPos(&ptNow);
            if (ptNow.x != ptLast.x || ptNow.y != ptLast.y)
            {
                ptLast = ptNow;
                bTrapped = FALSE;
                SetTimer(hWnd, IDT_CURSOR, TRAP_DELAY, (TIMERPROC)NULL);
            }
            break;
        }

        case WM_TIMER:
        {
            POINT ptNow;
            GetCursorPos(&ptNow);
            if (ptNow.x == ptLast.x && ptNow.y == ptLast.y)
            {
                RECT rc;
                GetClientRect(hWnd, &rc);
                POINT ptTarget = {
                    (rc.right  - ICON_SIZE) / 2 + YIN_HOT_X,
                    (rc.bottom - ICON_SIZE) / 2 + YIN_HOT_Y
                };
                ClientToScreen(hWnd, &ptTarget);
                SetCursorPos(ptTarget.x, ptTarget.y);
                // Ignore the mouse-move notification generated by this repositioning.
                GetCursorPos(&ptLast);
                SetCursor(hYin);
                bTrapped = TRUE;
                KillTimer(hWnd, IDT_CURSOR);
            }
            else
            {
                // Movement outside this window does not generate WM_MOUSEMOVE here.
                ptLast = ptNow;
            }
            return 0;
        }

        case WM_SETCURSOR:
            if (bTrapped)
            {
                SetCursor(hYin);
                return TRUE;
            }
            break;

        case WM_DESTROY:
            KillTimer(hWnd, IDT_CURSOR);
            DestroyCursor(hYin);
            DestroyIcon(hYang);
            PostQuitMessage(0);
            return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

Creación de un cursor con mezcla alfa

Siga estos pasos para crear un cursor o icono combinado alfa en tiempo de ejecución:

  • Rellena un BITMAPINFOHEADER para un DIB de arriba abajo (biBitCount=32, biCompression=BI_RGB, biHeight negativos) y llama a CreateDIBSection para obtener el mapa de bits de color. Vuelva a usar el mismo encabezado con biBitCount=1 para crear la máscara 1bpp AND.
  • Dibuje la imagen del cursor en la sección DIB en color y establezca el byte alfa de cada píxel.
  • Rellene la máscara AND: establezca cada bit en 0 (opaco) donde alpha > 0y en 1 (transparente) donde alpha == 0.
  • Rellene una estructura ICONINFO con la sección DIB como hbmColor y la máscara AND como hbmMask.
  • Llame a CreateIconIndirect para crear el cursor o el icono.

En el código siguiente se muestra cómo crear un cursor combinado alfa. DrawRGBCircles escribe tres círculos semitransparentes de R/G/B superpuestos directamente en un búfer de píxeles: compone los círculos de forma analítica mediante la fórmula de AC_SRC_OVER y almacena canales RGB premultiplicados con alfa en rgbReserved. El tamaño del cursor se lee de GetSystemMetrics para que coincida con la configuración del tamaño del cursor del sistema. Cambie fIcon a TRUE para crear un icono en su lugar.

#define WIDTHBYTES(bits) ((DWORD)(((bits) + 31) & ~31) / 8)

// Fill pixels (top-down, cx-cy) with three overlapping R/G/B circles, each alpha=128.
// Composites with AC_SRC_OVER and preserves premultiplied RGB channels.
static void DrawRGBCircles(RGBQUAD *pixels, int cx, int cy)
{
    // r = cx/3: each center is r px from its nearest edge, so the circle touches exactly.
    const int r = cx / 3;
    struct { int x, y; RGBQUAD color; } circles[3] = {
        //                  B    G    R    A
        { cx / 2,  r,      {  0,   0, 255, 128 } },  // red,   top-center
        { r,       cy - r, {  0, 255,   0, 128 } },  // green, bottom-left
        { cx - r,  cy - r, {255,   0,   0, 128 } },  // blue,  bottom-right
    };

    for (int y = 0; y < cy; y++) {
        for (int x = 0; x < cx; x++) {
            DWORD R = 0, G = 0, B = 0, A = 0;
            for (int i = 0; i < 3; i++) {
                int dx = x - circles[i].x, dy = y - circles[i].y;
                if (dx*dx + dy*dy > r*r) continue;
                DWORD a = circles[i].color.rgbReserved, inv = 255 - a;
                // AC_SRC_OVER (premultiplied): dst = src + dst * (1 - src_a/255)
                R = circles[i].color.rgbRed   * a / 255 + R * inv / 255;
                G = circles[i].color.rgbGreen * a / 255 + G * inv / 255;
                B = circles[i].color.rgbBlue  * a / 255 + B * inv / 255;
                A = a                                    + A * inv / 255;
            }
            RGBQUAD *p = &pixels[y * cx + x];
            p->rgbReserved = (BYTE)A;
            p->rgbRed   = (BYTE)R;
            p->rgbGreen = (BYTE)G;
            p->rgbBlue  = (BYTE)B;
        }
    }
}

HCURSOR CreateAlphaCursor(void)
{
    int cx = GetSystemMetrics(SM_CXCURSOR);
    int cy = GetSystemMetrics(SM_CYCURSOR);

    // Extra RGBQUAD at the end for the 1bpp colour table (index 1 = white).
    BYTE bmiBuffer[sizeof(BITMAPINFO) + sizeof(RGBQUAD)] = {0};
    BITMAPINFO *pBmi = (BITMAPINFO *)bmiBuffer;
    pBmi->bmiHeader.biSize        = sizeof(pBmi->bmiHeader);
    pBmi->bmiHeader.biWidth       = cx;
    pBmi->bmiHeader.biHeight      = -cy;   // negative = top-down
    pBmi->bmiHeader.biPlanes      = 1;
    pBmi->bmiHeader.biCompression = BI_RGB;

    // Create a top-down 32bpp DIB section for the color (XOR) image.
    pBmi->bmiHeader.biBitCount = 32;
    RGBQUAD *lpBits;
    HBITMAP hBitmap = CreateDIBSection(NULL, pBmi, DIB_RGB_COLORS, (void **)&lpBits, NULL, 0);

    DrawRGBCircles(lpBits, cx, cy);

    // Reuse the same header for the 1bpp AND mask; switch biBitCount and set the colour table.
    pBmi->bmiHeader.biBitCount = 1;
    pBmi->bmiColors[0].rgbRed = pBmi->bmiColors[0].rgbGreen = pBmi->bmiColors[0].rgbBlue = 0;
    pBmi->bmiColors[1].rgbRed = pBmi->bmiColors[1].rgbGreen = pBmi->bmiColors[1].rgbBlue = 255;
    BYTE *andBits;
    HBITMAP hMaskBitmap = CreateDIBSection(NULL, pBmi, DIB_RGB_COLORS, (void **)&andBits, NULL, 0);

    // Set AND mask: bit=1 (transparent) wherever no circle was drawn.
    int maskStride = WIDTHBYTES(cx * 1);
    for (int y = 0; y < cy; y++) {
        for (int x = 0; x < cx; x++) {
            if (lpBits[y * cx + x].rgbReserved == 0)
                andBits[y * maskStride + x / 8] |= (BYTE)(0x80 >> (x % 8));
        }
    }

    ICONINFO ii = {0};
    ii.fIcon     = FALSE;
    ii.xHotspot  = cx / 2;
    ii.yHotspot  = cy / 2;
    ii.hbmMask   = hMaskBitmap;
    ii.hbmColor  = hBitmap;

    HCURSOR hCursor = (HCURSOR)CreateIconIndirect(&ii);

    DeleteObject(hBitmap);
    DeleteObject(hMaskBitmap);

    return hCursor;
}

Antes de cerrar, debe usar la función DestroyCursor para destruir los cursores creados con CreateCursor o CreateIconIndirect. No es necesario destruir cursores creados por otras funciones.

Obtener el tamaño del cursor

En el ejemplo siguiente se obtienen las dimensiones de un cursor o icono a partir de su identificador:

BOOL GetCursorDimensions(_In_ HCURSOR hcur, _Out_ SIZE *psiz)
{
    ICONINFO ii;
    BOOL fResult = GetIconInfo(hcur, &ii);
    if (fResult) {
        BITMAP bm;
        fResult = GetObject(ii.hbmMask, sizeof(bm), &bm) == sizeof(bm);
        if (fResult) {
            psiz->cx = bm.bmWidth;
            psiz->cy = ii.hbmColor ? bm.bmHeight : bm.bmHeight / 2;
        }
        DeleteObject(ii.hbmMask);
        if (ii.hbmColor) DeleteObject(ii.hbmColor);
    }
    return fResult;
}

Mostrar un cursor

El sistema muestra automáticamente el cursor de clase (el cursor asociado a la ventana a la que apunta el cursor). Puede asignar un cursor de clase al registrar una clase de ventana. En el ejemplo siguiente se muestra esto asignando un identificador de cursor al miembro hCursor de la estructura WNDCLASS identificada por el parámetro wc .

WNDCLASS wc = {0};
wc.lpfnWndProc   = MainWndProc;
wc.hInstance     = hInstance;
wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor       = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYICON));
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName  = TEXT("GenericMenu");
wc.lpszClassName = TEXT("GenericWClass");

return RegisterClass(&wc);

Cuando se registra la clase de ventana, el cursor identificado por IDC_MYICON en el archivo de definición de recursos de la aplicación es el cursor predeterminado para todas las ventanas basadas en la clase .

La aplicación puede cambiar el diseño del cursor mediante la función SetCursor y especificando un identificador de cursor diferente. Sin embargo, cuando se mueve el cursor, el sistema vuelve a dibujar el cursor de clase en la nueva ubicación. Para evitar que se vuelva a dibujar el cursor de clase, debe procesar el mensaje WM_SETCURSOR . Cada vez que el cursor se mueve y la entrada del mouse no se captura, el sistema envía este mensaje a la ventana en la que se mueve el cursor.

Puede especificar diferentes cursores para diferentes condiciones al procesar WM_SETCURSOR. Marca LOWORD(lParam) para distinguir el área de cliente de las áreas que no son de cliente, como los controladores de redimensionamiento; pasa los eventos que no sean de cliente a DefWindowProc para que el sistema establezca allí el cursor de sistema adecuado.

case WM_SETCURSOR:
    if (LOWORD(lParam) == HTCLIENT)
    {
        SetCursor(hCurs3);
        return TRUE;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);

Devolver TRUE impide que DefWindowProc restablezca el cursor al cursor de clase. Al pasar los eventos que no sean de cliente a DefWindowProc, se conservan los cursores de redimensionamiento y desplazamiento del sistema.

Puede reemplazar un cursor de clase mediante la función SetClassLongPtr . Esta función cambia la configuración de ventana predeterminada para todas las ventanas de una clase especificada. En el ejemplo siguiente se reemplaza el cursor de clase existente por el hCurs2 cursor.

SetClassLongPtr(hWnd, GCLP_HCURSOR, (LONG_PTR)hCurs2);

Para obtener más información, vea Clases de ventana y entrada del mouse.

Confinar un cursor

En el ejemplo siguiente se limita el cursor a la ventana de la aplicación y, a continuación, se restaura el cursor a su ventana anterior. En el ejemplo se usa la función GetClipCursor para registrar el área en la que el cursor se puede mover y la función ClipCursor para limitar y restaurar el cursor.

RECT rcOldClip;
GetClipCursor(&rcOldClip);

RECT rcClip;
GetWindowRect(hWnd, &rcClip);
ClipCursor(&rcClip);

// ... process input from the confined cursor ...

ClipCursor(&rcOldClip);

Dado que solo hay un cursor a la vez disponible en el sistema, una aplicación que limita el cursor debe restaurar el cursor antes de renunciar al control a otra ventana.

Usar el teclado para mover el cursor

Dado que el sistema no requiere un mouse, una aplicación debe poder simular acciones del mouse con el teclado. En el ejemplo siguiente se muestra cómo lograrlo mediante las funciones GetCursorPos y SetCursorPos y mediante el procesamiento de la entrada de las teclas de dirección.

static int nRepeat = 1;

switch (uMsg)
{
    case WM_KEYDOWN:
    {
        int dx = 0, dy = 0;
        switch (wParam)
        {
            case VK_LEFT:  dx = -nRepeat; break;
            case VK_RIGHT: dx = +nRepeat; break;
            case VK_UP:    dy = -nRepeat; break;
            case VK_DOWN:  dy = +nRepeat; break;
            default: return DefWindowProc(hWnd, uMsg, wParam, lParam);
        }

        POINT pt;
        GetCursorPos(&pt);
        ScreenToClient(hWnd, &pt);
        pt.x += dx;
        pt.y += dy;

        RECT rc;
        GetClientRect(hWnd, &rc);
        if (pt.x < rc.left)         pt.x = rc.left;
        else if (pt.x >= rc.right)  pt.x = rc.right - 1;
        if (pt.y < rc.top)          pt.y = rc.top;
        else if (pt.y >= rc.bottom) pt.y = rc.bottom - 1;

        ClientToScreen(hWnd, &pt);
        SetCursorPos(pt.x, pt.y);
        if (nRepeat < 32) nRepeat++;
        return 0;
    }
    case WM_KEYUP:
        nRepeat = 1;
        return 0;
}