Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tej sekcji omówiono następujące tematy.
- Tworzenie kursora
- Używanie funkcji kursora do tworzenia pułapki na myszy
- Tworzenie kursora z mieszaniem alfa
- Uzyskiwanie rozmiaru kursora
- wyświetlanie kursora
- Ograniczanie kursora
- Używanie klawiatury do przesuwania kursora
Tworzenie kursora
Poniższy przykład tworzy dwa uchwyty kursora: jeden dla standardowego kursora klepsydry i jeden dla kursora niestandardowego dołączonego jako zasób w pliku definicji zasobów aplikacji.
HCURSOR hCurs1 = LoadCursor(NULL, IDC_WAIT);
HCURSOR hCurs2 = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYICON));
Aplikacje powinny implementować niestandardowe kursory jako zasoby i używać LoadCursor, LoadCursorFromFilelub LoadImage zamiast tworzyć kursor w czasie wykonywania. Korzystanie z zasobów kursora pozwala uniknąć zależności urządzenia, upraszcza lokalizację i umożliwia aplikacjom udostępnianie projektów kursorów.
W poniższym przykładzie użyto funkcji CreateCursor, aby utworzyć niestandardowy kursor monochromatyczny w czasie wykonywania. W tym przykładzie pokazano, jak system interpretuje maski kursorów.
Każdy piksel na mapie kursora jest reprezentowany przez pojedynczy znak:
| Symbol | AND bit | Bit XOR | Wyświetlacz |
|---|---|---|---|
|
1 | 0 | Transparent |
X |
1 | 1 | Odwróć ekran |
o |
0 | 1 | Biały |
+ |
0 | 0 | Czarny |
#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);
Aby uzyskać więcej informacji, zobacz Mapy bitowe.
Tworzenie pułapki na myszy za pomocą funkcji kursora
W poniższym przykładzie użyto funkcji SetCursorPos, GetCursorPos, CreateCursor, CreateIcon, SetCursor i DrawIconEx, aby utworzyć prostą pułapkę myszy. Na środku okna jest wyświetlana ikona yin-yang. Jeśli kursor nie poruszy się przez 3 sekundy, przyciągnie się do ikony yang i zmieni kształt na yin. Przesunięcie myszy resetuje pułapkę.
// 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);
}
Tworzenie kursora z kanałem alfa
Wykonaj następujące kroki, aby utworzyć kursor lub ikonę z blendowaniem alfa w czasie wykonywania:
- Wypełnij strukturę BITMAPINFOHEADER dla top-down DIB (
biBitCount=32,biCompression=BI_RGB,biHeightujemne) i wywołaj funkcję CreateDIBSection, aby uzyskać bitmapę kolorów. Użyj ponownie tego samego nagłówka z elementembiBitCount=1, aby utworzyć maskę AND 1bpp. - Narysować obraz kursora w sekcji DIB kolorów i ustawić bajt alfa dla każdego piksela.
- Wypełnij maskę AND: ustaw dla każdego bitu wartość 0 (nieprzezroczystą), gdzie
alpha > 0, i na wartość 1 (przezroczystą), gdziealpha == 0. - Wypełnij strukturę ICONINFO sekcją DIB jako
hbmColori maską AND jakohbmMask. - Wywołaj metodę CreateIconIndirect , aby utworzyć kursor lub ikonę.
Poniższy kod pokazuje, jak utworzyć kursor mieszany alfa.
DrawRGBCircles zapisuje trzy nakładające się półprzezroczyste koła R/G/B bezpośrednio do bufora pikseli: komponuje te koła analitycznie z użyciem formuły AC_SRC_OVER i przechowuje kanały RGB przemnożone przez alfę wraz z kanałem alfa w rgbReserved. Rozmiar kursora jest odczytywany z getSystemMetrics w celu dopasowania ustawienia rozmiaru kursora systemowego. Zmień fIcon na TRUE, aby zamiast tego utworzyć ikonę.
#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;
}
Przed zamknięciem należy użyć funkcji DestroyCursor, aby zniszczyć wszystkie kursory utworzone za pomocą CreateCursor lub CreateIconIndirect. Nie jest konieczne zniszczenie kursorów utworzonych przez inne funkcje.
Pobieranie rozmiaru kursora
Poniższy przykład pobiera wymiary kursora lub ikony z uchwytu:
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;
}
Wyświetlanie kursora
System automatycznie wyświetla kursor klasy (kursor skojarzony z oknem, do którego wskazuje kursor). Kursor klasy można przypisać podczas rejestrowania klasy okna. Poniższy przykład ilustruje to przez przypisanie uchwytu kursora do hCursor członka WNDCLASS struktury zidentyfikowanej przez parametr 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);
Po zarejestrowaniu klasy okna kursor zidentyfikowany IDC_MYICON w pliku definicji zasobów aplikacji jest domyślnym kursorem dla wszystkich okien opartych na klasie.
Aplikacja może zmienić projekt kursora przy użyciu funkcji SetCursor i określania innego uchwytu kursora. Jednak po przesunięciu kursora system ponownie rysuje kursor klasy w nowym miejscu. Aby zapobiec ponownemu rysowaniu kursora klasy, należy przetworzyć komunikat WM_SETCURSOR. Za każdym razem, gdy kursor porusza się, a dane wejściowe myszy nie są przechwytywane, system wysyła ten komunikat do okna, w którym przesuwa się kursor.
Podczas przetwarzania WM_SETCURSORmożna określić różne kursory dla różnych warunków. Sprawdź LOWORD(lParam), aby odróżnić obszar klienta od obszarów nieklienckich, takich jak uchwyty zmiany rozmiaru okna; przekaż trafienia w obszarze nieklienckim do DefWindowProc, aby system ustawił tam odpowiedni kursor systemowy.
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(hCurs3);
return TRUE;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
Zwrócenie wartości TRUE uniemożliwia funkcji DefWindowProc zresetowanie kursora na kursor klasy. Przekazywanie trafień innych niż klient do defWindowProc zachowuje rozmiar systemu i przesuwa kursory.
Kursor klasy można zastąpić za pomocą funkcji SetClassLongPtr . Ta funkcja zmienia domyślne ustawienia okna dla wszystkich okien określonej klasy. Poniższy przykład zastępuje istniejący kursor klasy kursorem hCurs2.
SetClassLongPtr(hWnd, GCLP_HCURSOR, (LONG_PTR)hCurs2);
Aby uzyskać więcej informacji, zobacz Klasy okien oraz Dane wejściowe myszy.
Ograniczenie kursora
Poniższy przykład ogranicza kursor do okna aplikacji, a następnie przywraca kursor do poprzedniego okna. W przykładzie użyto funkcji GetClipCursor, aby zarejestrować obszar, w którym kursor może się poruszać, a funkcja ClipCursor, aby ograniczyć i przywrócić kursor.
RECT rcOldClip;
GetClipCursor(&rcOldClip);
RECT rcClip;
GetWindowRect(hWnd, &rcClip);
ClipCursor(&rcClip);
// ... process input from the confined cursor ...
ClipCursor(&rcOldClip);
Ponieważ w systemie jest dostępny tylko jeden kursor, aplikacja, która ogranicza kursor, musi przywrócić kursor przed ponownym zrezygnowaniem z kontrolki do innego okna.
Przenoszenie kursora za pomocą klawiatury
Ponieważ system nie wymaga myszy, aplikacja powinna być w stanie symulować akcje myszy za pomocą klawiatury. W poniższym przykładzie pokazano, jak to osiągnąć przy użyciu GetCursorPos i funkcji SetCursorPos oraz przetwarzania danych wejściowych z strzałek.
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;
}