Windows 2000 以降ではマウスカーソルにこのように影が付くようになりましたが、プログラム内で自作のカーソルを使用した場合はこのように影が付きません。
そこで、自作のカーソルにも影を付ける方法を紹介します。
ただし予め断っておきますが、実質的な意味はほとんどありませんし、あまり行儀のいい方法ではありません。
あるウィンドウに自作カーソルを使用する場合、通常 LoadCursor 関数などを使用してカーソルを読み込むか、CreateCursor 関数などを使用してカーソルを作成して、SetCursor 関数で設定します。
しかし、この方法ではカーソルに影が付いてくれません。
恐らく Windows からは各プログラムのカーソルに影を付けていいのかいけないのか分からないので、互換性のためにそうなっているのでしょう。
ではカーソルに影を付けたい場合はどうすればいいでしょうか。
SetCursorShadow といった API 関数が用意されていればいいのですが、そのようなものはありません。
そこで SetSystemCursor という API 関数を使用します。
この関数はその名の通りシステムのカーソルを変更する関数ですが、SetCursor と違いこの関数でカーソルを設定した場合、カーソルに影が付きます。
この関数での変更は一時的なもので、再びこの関数を呼ぶか、SystemParametersInfo 関数に SPI_SETCURSORS を渡して呼ぶか、Windows を再起動するとカーソルは元に戻ります。
SetSystemCursor を使用してカーソルを変更するコードは以下のようになります。
#define OEMRESOURCE // OCR_??? の定義を利用するためにはこの定義が必要です
#include <windows.h> // いつものヘッダ
#include <tchar.h>
static HINSTANCE hInst; // プログラムのインスタンスハンドルです
static HCURSOR hcurOld = NULL; // 元のカーソルを保持する変数です
// カーソルを設定する関数です
BOOL SetMyCursor(void)
{
HCURSOR hcur;
// 自作のカーソルをリソースから読み込んで複製します
hcur = CopyCursor(LoadCursor(hInst, TEXT("IDC_MYCURSOR")));
if (hcur != NULL) {
// 現在のカーソルを取得して複製します
hcurOld = CopyCursor(LoadCursor(NULL, IDC_ARROW));
if (hcurOld != NULL) {
// カーソルを自作のものに変えます
if (SetSystemCursor(hcur, OCR_NORMAL))
return TRUE;
// 変えられなかったのでリソースを開放します
DestroyCursor(hcurOld);
hcurOld = NULL;
}
DestroyCursor(hcur);
}
return FALSE;
}
// カーソルを元に戻す関数です
void RestoreCursor(void)
{
// カーソルを変更していた場合は元に戻します
if (hcurOld != NULL) {
SetSystemCursor(hcurOld, OCR_NORMAL);
hcurOld = NULL;
}
}
まず LoadCursor を使用してリソースからカーソルを読み込んでいます。
次に読み込んだカーソルを CopyCursor を使用して複製しています。
なぜ複製するかと言うと、SetSystemCursor で設定したカーソルはシステムが所有することになるので、プログラムの側のカーソルとは別のものにしなければならないからです。
次に LoadCursor を使用して、今度は現在のカーソルを取得して複製しています。
こうすることでカーソルを復元できるようにしているのですが、複製しているのは SetSystemCursor でカーソルを設定した段階で、前のカーソルが破棄されてしまうためです。
しかしシステムのカーソルを変更するのですから、単純に使っただけでは当然他のプログラムのカーソルまで変わってしまうことになります。
自分のウィンドウ内にカーソルが入ってきたら自作カーソルに変え、ウィンドウからカーソルが出た時にカーソルを元に戻す必要があります。
その為には、ウィンドウプロシージャで WM_MOUSEMOVE と WM_MOUSELEAVE を処理します。
具体的には以下のようなコードになります。
// ウィンドウプロシージャです
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// カーソルを変更したかどうかを保持する変数です
static BOOL fChangeCursor = FALSE;
switch (uMsg) {
case WM_MOUSEMOVE:
// カーソルがウィンドウのクライアント領域内で移動した
if (!fChangeCursor) {
// カーソルがウィンドウの外に出た時に、WM_MOUSELEAVE で知らせてもらえるようにします
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hwnd;
if (TrackMouseEvent(&tme)) {
SetMyCursor();
fChangeCursor = TRUE;
}
}
return 0;
case WM_MOUSELEAVE:
// カーソルがウィンドウ外に出たので元に戻します
RestoreCursor();
fChangeCursor = FALSE;
return 0;
case WM_DESTROY:
// 念のために終了時にもカーソルを元に戻す処理を入れておきます
RestoreCursor();
// プログラムを終了させます
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// エントリポイントです
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR pszCmdLine, int nCmdShow)
{
// ウィンドウクラス名です
static const LPCTSTR pszWindowClass = TEXT("MyCursor Sample Window");
// インスタンスハンドルを保持します
hInst = hInstance;
// ウィンドウクラスを登録します
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = pszWindowClass;
RegisterClass(&wc);
// ウィンドウを作成します
HWND hwnd = CreateWindowEx(0, pszWindowClass, TEXT("Sample"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 256, 256,
NULL, NULL, hInst, NULL);
if (hwnd == NULL) {
MessageBox(NULL, TEXT("ウィンドウが作成できません。"), NULL, MB_OK | MB_ICONSTOP);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// メッセージループです
MSG msg;
BOOL Result;
while ((Result = GetMessage(&msg, NULL, 0, 0)) != 0 && Result != -1) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
実行結果は以下のようになり、ちゃんと影が付いています。
これでカーソルに影を付けることができました。
しかし、プログラムが異常終了した場合にカーソルが元に戻らない可能性があります。
それが気になるのであれば、異常終了時にカーソルを元に戻す処理を追加します。
以下のように記述すればいいでしょう。
// 例外発生時に呼ばれる関数です
LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo)
{
// カーソルを元に戻します
SystemParametersInfo(SPI_SETCURSORS, 0, NULL, 0);
return EXCEPTION_CONTINUE_SEARCH;
}
// エントリポイントです
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR pszCmdLine, int nCmdShow)
{
// 例外ハンドラを登録します
SetUnhandledExceptionFilter(ExceptionFilter);
// 以下は前のコードと同じです
先に少し触れましたが、SystemParametersInfo 関数に SPI_SETCURSORS を指定して呼び出すと、カーソルがシステムで設定されているものに戻ります。
先に示した SetMyCursor では古いカーソルを複製しておいて RestoreCursor で元に戻す処理を記述していますが、実はこれを使えばそんなことをしなくてもいい訳です。
さて、ここまでカーソルに影を付ける方法を紹介しましたが、はっきり言ってただカーソルに影を付けるだけのためにこんなコードを書くのは無駄です(とはいえ、人間は無駄なことが大好きなのですが)。
しかも、システムのカーソルを変更するという行儀の悪いことを行っています。
ですので通常はこんなことはしない方がいいのですが、「どうしてもカーソルに影がないと気が済まない!」という方は(もし居れば)やってみてください。