カーソルの使用

このセクションでは、次のトピックについて説明します。

カーソルの作成

次の例では、標準の砂時計カーソル用と、アプリケーションのリソース定義ファイルにリソースとして含まれるカスタム カーソル用の 2 つのカーソル ハンドルを作成します。

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

アプリケーションでは、リソースとしてカスタム カーソルを実装し、実行時にカーソルを作成するのではなく、 LoadCursor、 LoadCursorFromFile、または LoadImage を使用する必要があります。 カーソル リソースを使用すると、デバイスへの依存を回避し、ローカライズを簡略化し、アプリケーションでカーソルデザインを共有できます。

次の例では、 CreateCursor 関数を使用して、実行時にカスタム モノクロ カーソルを作成します。 この例は、システムがカーソル マスクを解釈する方法を示すためにここに含まれています。

カーソル マップ内の各ピクセルは、1 文字で表されます。

Symbol ANDビット XOR ビット ディスプレイ
1 0 透明
X 1 1 画面を反転する
o 0 1 White
+ 0 0 黒い
#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);

詳細については、「 ビットマップ」を参照してください。

カーソル関数を使用してマウストラックを作成する

次の例では、 SetCursorPos、 GetCursorPos、 CreateCursor、 CreateIcon、 SetCursor、 DrawIconEx 関数を使用して、単純なマウストラックを作成します。 ウィンドウの中央に陽の形のアイコンが描画されます。 カーソルが 3 秒間移動していない場合は、陽アイコンにスナップされ、yin 図形に変わります。 マウスを動かすと、トラップがリセットされます。

// 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);
}

アルファ ブレンド カーソルの作成

実行時にアルファ ブレンド カーソルまたはアイコンを作成するには、次の手順に従います。

  • トップダウン DIB (biHeight、、 が負) 用に biCompression=BI_RGB を設定し、CreateDIBSection を呼び出してカラー ビットマップを取得します。 同じヘッダーを biBitCount=1 と共に再利用して、1bpp AND マスクを作成します。
  • カラー DIB セクションにカーソルイメージを描画し、各ピクセルのアルファ バイトを設定します。
  • AND マスクを塗りつぶす: 各ビットを 0 (不透明) ( alpha > 0) に設定し、 alpha == 0場合は 1 (透明) に設定します。
  • ICONINFO 構造体に DIB セクションをhbmColor、AND マスクをhbmMaskとして入力します。
  • CreateIconIndirect を呼び出して、カーソルまたはアイコンを作成します。

次のコードは、アルファ ブレンド カーソルを作成する方法を示しています。 DrawRGBCircles は、重複する 3 つの R/G/B 半透明の円をピクセル バッファーに直接書き込みます。AC_SRC_OVER式を使用して円を分析的に合成し、アルファを持つ事前乗算された RGB チャネルを rgbReservedに格納します。 カーソル サイズは、システム カーソル サイズの設定に合わせて GetSystemMetrics から読み取られます。 fIconをTRUEに変更して、代わりにアイコンを作成します。

#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;
}

終了する前に、 DestroyCursor 関数を使用して 、CreateCursor または CreateIconIndirect で作成したすべてのカーソルを破棄する必要があります。 他の関数によって作成されたカーソルを破棄する必要はありません。

カーソル サイズの取得

次の例では、カーソルまたはアイコンのサイズをハンドルから取得します。

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;
}

カーソルの表示

クラス カーソル (カーソルが指しているウィンドウに関連付けられたカーソル) が自動的に表示されます。 ウィンドウ クラスの登録中にクラス カーソルを割り当てることができます。 次の例は、wc パラメーターで識別される WNDCLASS 構造体の hCursor メンバーにカーソル ハンドルを割り当てることでこれを示しています。

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);

ウィンドウ クラスが登録されると、アプリケーションのリソース定義ファイル内の IDC_MYICON によって識別されるカーソルは、クラスに基づくすべてのウィンドウの既定のカーソルになります。

アプリケーションでは、 SetCursor 関数を使用し、別のカーソル ハンドルを指定することで、カーソルのデザインを変更できます。 ただし、カーソルが移動すると、新しい場所でクラス カーソルが再描画されます。 クラス カーソルが再描画されないようにするには、 WM_SETCURSOR メッセージを処理する必要があります。 カーソルが移動し、マウス入力がキャプチャされないたびに、システムはカーソルが移動しているウィンドウにこのメッセージを送信します。

WM_SETCURSORの処理中に、条件ごとに異なるカーソル を指定できます。 LOWORD(lParam)チェックして、サイズ変更ハンドルなどのクライアント領域以外の領域とクライアント領域を区別します。クライアント以外のヒットを DefWindowProc に渡して、システムがそこに適切なシステム カーソルを設定します。

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

TRUEを返した場合、DefWindowProc がクラス カーソルにカーソルをリセットできなくなります。 クライアント以外のヒットを DefWindowProc に渡すと、システムのサイズ変更とカーソルの移動が維持されます。

SetClassLongPtr 関数を使用して、クラス カーソルを置き換えることができます。 この関数は、指定したクラスのすべてのウィンドウの既定のウィンドウ設定を変更します。 次の例では、既存のクラス カーソルを hCurs2 カーソルに置き換えます。

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

詳細については、「 ウィンドウ クラス と マウス入力」を参照してください。

カーソルの制限

次の例では、カーソルをアプリケーションのウィンドウに限定し、カーソルを前のウィンドウに復元します。 この例では 、GetClipCursor 関数を使用して、カーソルを移動できる領域を記録し、 ClipCursor 関数を使用してカーソルを閉じ込め、復元します。

RECT rcOldClip;
GetClipCursor(&rcOldClip);

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

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

ClipCursor(&rcOldClip);

システムでは一度に 1 つのカーソルしか使用できないため、カーソルを閉じ込めるアプリケーションは、別のウィンドウに制御を放棄する前にカーソルを復元する必要があります。

キーボードを使用してカーソルを移動する

システムはマウスを必要としないため、アプリケーションはキーボードを使用してマウス操作をシミュレートできる必要があります。 次の例は、GetCursorPos 関数と SetCursorPos 関数を使用し、方向キーからの入力を処理することによってこれを実現する方法を示しています。

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;
}