第211章 スクロールバーとキーストローク


今回は、前章で作ったプログラムをもう少し改良します。 たいていのプログラムでは、スクロールバーが表示された時 マウス操作以外にキーボードからスクロールバーを操作することができます。 よく使われるのが、カーソルキー、PgDn, PgUp, Home, Endキー などです。今回はこれらのキーが押された時の処理を行います。



では、さっそくプログラムをみてみましょう。

リソース・スクリプトは同じなので省略します。

// fltscb03.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <commctrl.h> #include "resource.h" #define LINESIZE 20 //1行の高さ #define MAXLINE 10 //行数 #define CHARWIDTH 8 //1文字の幅 #define CHARNUMBER 38//一行の文字数 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); char szClassName[] = "fltscb03"; //ウィンドウクラス int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるFlat Scroll Bar",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

ここも、前章と同じです。コモンコントロールの準備を忘れないでください。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, i, len, dx, dy; HDC hdc; PAINTSTRUCT ps; char *str_org = "%2d: 第%2d行目 猫でもわかるスクロールバー"; char str[256]; static SCROLLINFO si, sih; static int dispno, pos, dispnoh, posh; static int nStyle; //0:Flat 1:Encarta 2:Old static HMENU hMenu; SHORT sKeyCheck; BOOL bVScroll, bHScroll;//ScrollBarが表示されているかどうか RECT rc; switch (msg) { case WM_CREATE: InitializeFlatSB(hWnd); FlatSB_SetScrollProp(hWnd, WSB_PROP_VSTYLE, FSB_FLAT_MODE, TRUE); FlatSB_SetScrollProp(hWnd, WSB_PROP_HSTYLE, FSB_FLAT_MODE, TRUE); hMenu = GetMenu(hWnd); break; case WM_INITMENU: switch (nStyle) { case 0: EnableMenuItem(hMenu, IDM_FLAT, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_ENCARTA, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, IDM_OLD, MF_BYCOMMAND | MF_ENABLED); break; case 1: EnableMenuItem(hMenu, IDM_ENCARTA, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_FLAT, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, IDM_OLD, MF_BYCOMMAND | MF_ENABLED); break; case 2: EnableMenuItem(hMenu, IDM_OLD, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_FLAT, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, IDM_ENCARTA, MF_BYCOMMAND | MF_ENABLED); break; } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_OLD: FlatSB_SetScrollProp(hWnd, WSB_PROP_VSTYLE, FSB_REGULAR_MODE, TRUE); FlatSB_SetScrollProp(hWnd, WSB_PROP_HSTYLE, FSB_REGULAR_MODE, TRUE); nStyle = 2; break; case IDM_FLAT: FlatSB_SetScrollProp(hWnd, WSB_PROP_VSTYLE, FSB_FLAT_MODE, TRUE); FlatSB_SetScrollProp(hWnd, WSB_PROP_HSTYLE, FSB_FLAT_MODE, TRUE); nStyle = 0; break; case IDM_ENCARTA: FlatSB_SetScrollProp(hWnd, WSB_PROP_VSTYLE, FSB_ENCARTA_MODE, TRUE); FlatSB_SetScrollProp(hWnd, WSB_PROP_HSTYLE, FSB_ENCARTA_MODE, TRUE); nStyle = 1; break; } break; case WM_SIZE: dispno = HIWORD(lp) / LINESIZE; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; si.nMin = 0; si.nMax = MAXLINE; si.nPage = dispno; si.nPos = pos; FlatSB_SetScrollInfo(hWnd, SB_VERT, &si, TRUE); dispnoh = LOWORD(lp) / CHARWIDTH; sih.cbSize = sizeof(SCROLLINFO); sih.fMask = SIF_ALL; sih.nMin = 0; sih.nMax = CHARNUMBER; sih.nPage = dispnoh; sih.nPos = posh; FlatSB_SetScrollInfo(hWnd, SB_HORZ, &sih, TRUE); break; case WM_VSCROLL: switch (LOWORD(wp)) { case SB_LINEUP: dy = -1; break; case SB_LINEDOWN: dy = 1; break; case SB_PAGEUP: dy = -1 * si.nPage; break; case SB_PAGEDOWN: dy = si.nPage; break; case SB_THUMBTRACK: dy = HIWORD(wp) - si.nPos; break; default: dy = 0; break; } dy = max(-1 * si.nPos, min(dy, si.nMax - si.nPos)); if (dy != 0) { si.nPos += dy; pos = si.nPos; FlatSB_SetScrollInfo(hWnd, SB_VERT, &si, TRUE); ScrollWindowEx(hWnd, 0, -dy * LINESIZE, NULL, NULL, NULL, NULL, 0); InvalidateRect(hWnd, NULL, TRUE); UpdateWindow(hWnd); } break; case WM_HSCROLL: switch (LOWORD(wp)) { case SB_LINEUP: dx = -1; break; case SB_LINEDOWN: dx = 1; break; case SB_PAGEUP: dx = -1 * sih.nPage; break; case SB_PAGEDOWN: dx = sih.nPage; break; case SB_THUMBTRACK: dx = HIWORD(wp) - sih.nPos; break; default: dx = 0; break; } dx = max(-1 * sih.nPos, min(dx, sih.nMax - sih.nPos)); if (dx != 0) { sih.nPos += dx; posh = sih.nPos; FlatSB_SetScrollInfo(hWnd, SB_HORZ, &sih, TRUE); ScrollWindowEx(hWnd, -dx * CHARWIDTH, 0, NULL, NULL, NULL, NULL, 0); InvalidateRect(hWnd, NULL, TRUE); UpdateWindow(hWnd); } break; case WM_KEYDOWN: GetClientRect(hWnd, &rc); if (rc.right - rc.left < (sih.nMax - sih.nMin) * CHARWIDTH) bHScroll = TRUE; else bHScroll = FALSE; if (rc.bottom - rc.top < (si.nMax - si.nMin) * LINESIZE) bVScroll = TRUE; else bVScroll = FALSE; if (bVScroll == FALSE && bHScroll == FALSE) return DefWindowProc(hWnd, msg, wp, lp); switch (wp) { case VK_UP: if (bVScroll) SendMessage(hWnd, WM_VSCROLL, SB_LINEUP, 0); break; case VK_DOWN: if (bVScroll) SendMessage(hWnd, WM_VSCROLL, SB_LINEDOWN, 0); break; case VK_RIGHT: if (bHScroll) SendMessage(hWnd, WM_HSCROLL, MAKELONG(SB_LINEDOWN, 0), 0); break; case VK_LEFT: if (bHScroll) SendMessage(hWnd, WM_HSCROLL, MAKELONG(SB_LINEUP, 0), 0); break; case VK_PRIOR: if (bVScroll) SendMessage(hWnd, WM_VSCROLL, MAKELONG(SB_PAGEUP, 0), 0); break; case VK_NEXT: if (bVScroll) SendMessage(hWnd, WM_VSCROLL, MAKELONG(SB_PAGEDOWN, 0), 0); break; case VK_HOME: sKeyCheck = GetKeyState(VK_SHIFT); if (sKeyCheck & 0x8000) { si.nPos = 0; pos = 0; sih.nPos = 0; posh = 0; FlatSB_SetScrollInfo(hWnd, SB_VERT, &si, TRUE); FlatSB_SetScrollInfo(hWnd, SB_HORZ, &sih, TRUE); InvalidateRect(hWnd, NULL, TRUE); } else return (DefWindowProc(hWnd, msg, wp, lp)); break; case VK_END: sKeyCheck = GetKeyState(VK_SHIFT); if (sKeyCheck & 0x8000) { si.nPos = si.nMax; pos = si.nMax; sih.nPos = sih.nMax; posh = sih.nMax; FlatSB_SetScrollInfo(hWnd, SB_VERT, &si, TRUE); FlatSB_SetScrollInfo(hWnd, SB_HORZ, &sih, TRUE); InvalidateRect(hWnd, NULL, TRUE); } else return (DefWindowProc(hWnd, msg, wp, lp)); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); for (i = 0 ;i <= dispno; i++) { if (i + si.nPos <= MAXLINE) { len = wsprintf(str, str_org, i+si.nPos, i+si.nPos); TextOut(hdc, 10 - sih.nPos * CHARWIDTH, i * LINESIZE, str, len); } } EndPaint(hWnd, &ps); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_KEYDOWNメッセージの処理をみて下さい。 このメッセージそのものについては、 すでに第33章で解説してあります。 キーが押された時に、まずクライアント領域の大きさを調べます。 横幅が表示するものより小さければ、水平スクロールバーが表示されているので bHScroll変数をTRUEにします。高さについても同様に調べます。 水平、垂直スクロールバーともに表示されていなければ、このプログラムでは キー押下の処理は必要ないので、DefWindowProcに渡します。
どちらかのスクロールバーが表示されていたらwpを調べて押されたキーを 知ります。たとえば上矢印キーが押されたら自分自身にWM_VSCROLLメッセージを 発します。この時wParamをSB_LINEUPにしてやれば1行スクロールアップします。 (すでに自分でWM_VSCROLメッセージのところでSB_LINEUPの処理を書いてある場合) 後は同様にVK_DOWN, VK_RIGHT, VK_LEFT, VK_PRIOR, VK_NEXTの処理を 行います。

さて、VK_HOMEキーが押された時はシフトキーが同時に押されているかどうかを調べて、 シフトキーが押されている場合は文書の最初を表示するようにしています。

SHORT GetKeyState( int nVirtKey // 仮想キーコード );

この関数は指定された仮想キーコードの状態を取得します。 nVirtKeyにVK_SHIFTを指定すると左右の区別なくシフトキーの 状態を知ることができます。
戻り値の最上位ビットが1の時はキーが押されていて、0の時は 押されていないことを意味します。トグルキー(CAPとかカナキーなど) の場合は最下位ビットが1の時はトグル状態、0の時はトグル解除 状態であることを意味します。

さて、最上位ビットが1かどうかを調べるには戻り値と二進法の 1000000000000000(16進の8000)の&(ビットごとのAND演算)を 求めればよいです。

SHIFT+VK_HOMEが押されていることがわかったら文書の一番最初を 表示します。このときSCROLLINFO構造体の内容を矛盾のないものにしておきます。 また、pos, posh変数を0にしておくことを忘れないでください。

VK_ENDについても同様の処理をします。

今回は簡単でした。しかし、このプログラムには少々不満があります。 たとえば垂直スクロールバーが出ている時に、スクロールアップしていくと 最後の1行が最上方に行くまでスクロールします。この状態で ウィンドウサイズを大きくするとスクロールバーは消えて 文書は最後の1行のみが表示された状態となります。 水平方向についても同様のことが言えます。 たとえば、メモ帳などは最後の行が最下部までスクロールすると そこで、スクロールがストップします。このほうが使いやすいような気がします。 (ただし、VC++のエディタは最後の行が最上部に行くまでスクロールします)

いろいろ改良してみて下さい。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 30/Jun/1999 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。