スクロールバー


ウィンドウをスクロールする

GUI部品の中で、もっとも威力を発揮する部品の一つにスクロールバーがあります
これを使うことによって、ウィンドウのサイズや解像度に依存することなく
大きな画像や長い文字を扱うことができるようになります

このメリットは、後に説明する様々なグラフィックプログラムに応用できるでしょう
因みに、このようなGUI部品をWindowsではコントロールと呼びます
ただし、ボタンやスクロールバーをコントロールと呼ぶのは Windows だけです
Java システムなどではGUI部品を「コンポーネント」と呼びます
ので、Windows以外のプログラマとGUI話をする時は注意してください

ウィンドウに関連付けたスクロールバーは、簡単に実現することができます
CreateWindow() 関数の第三引数のスタイルにスクロールバーを付けるよう指示します
水平スクロールバーは WS_HSCROLL、垂直スクロールバーは WS_VSCROLL です

もちろんこれだけでは、スクロールバーは表示されるものの使い物になりません
スクロールバーの各種設定は SetScrollInfo() で行われる
int SetScrollInfo(
	HWND hwnd , int fnBar ,
	LPSCROLLINFO lpsi , BOOL fRedraw
);
hwnd には、ウィンドウのハンドルを指定します
//スクロールバーコントロールのハンドルを指定することもあるが、これは後記します

fnBar には、対象となるスクロールバーを指定します
今回は水平 SB_HORZか、垂直 SB_VERTのいずれかのバーになります

lpsi には、スクロールバーの情報が入った SCROLLINFO 構造体へのポインタを指定します
fRedraw をTRUEにするとバーを再描画し、FALSEだと再描画しません
戻り値は、スクロールバーの現在の位置が返ります

SCROLLINFO 構造体とは、次のようになっています
typedef struct tagSCROLLINFO {  // si 
    UINT cbSize; 
    UINT fMask; 
    int  nMin; 
    int  nMax; 
    UINT nPage; 
    int  nPos; 
    int  nTrackPos; 
}   SCROLLINFO;
cbSize には、この構造体のサイズをバイト単位で格納します
これは sizeof() 演算子を用いてこのメンバに自分自身のサイズを代入すれば良いです

fMask には、以下の値をセットするかしないかを表すフラグを指定します
各フラグは OR 演算子で複数指定することができます

定数解説
SIF_ALL SIF_PAGE , SIF_POS
SIF_RANGE , と SIF_TRACKPOS の組み合わせ
SIF_DISABLENOSCROLL この値はスクロールバーの値をセットする時に限り使われる
もし、無効なパラメータが指定されても消去せずに使用不能にします
SetScrollInfo() 専用
SIF_PAGE nPage を設定することを明示します
SIF_POS nPos を設定することを明示します
SIF_RANGE nMin と nMax を設定することを明示します
SIF_TRACKPOS nTrackPos フィールドにつまみの位置をセットすることを明示
GetScrollInfo() 専用

nMin はスクロールする最小値、nMax は最大値を表します
これをセットするには fMask で SIF_RANGE を指定する必要があります

nPage はページサイズと呼ばれる値を設定します
知っていると思いますが、現在のWindowsスクロールバーはつまみの大きさが
クライアントエリア(ページ)の大きさで変化します

ページサイズは、このスクロールバーのつまみの大きさを表す値で
ページサイズと最大値(スクロール幅)が同じの時 1 : 1 の関係になります

ただし、スクロール幅が 0 〜 9 として ページサイズが 10 以上の場合
スクロールバーは 不要 になってしまう
このときWindowsの習慣としてスクロールバーは削除されようとしますが
fMask で SIF_DISABLENOSCROLL を指定していれば削除されることはありません

nPos はスクロールバーのつまみ(スクロールボックス)の現在の位置
nTrackPos は現在のトラッキング位置ですが、SetScrollInfo() は無視します
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	SCROLLINFO scr;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		scr.cbSize = sizeof(SCROLLINFO);
		scr.fMask = SIF_PAGE | SIF_RANGE;
		scr.nMin = 0;
		scr.nMax = 9;
		scr.nPage = 2;

		SetScrollInfo(hwnd , SB_VERT , &scr , TRUE);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_VSCROLL ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
	return msg.wParam;
}


ウィンドウの右側に、垂直スクロールバーが付加されていることがわかりますね
ウィンドウのサイズを変更すると、比例してスクロールボックスの大きさも変わります

ただし、このプログラムのスクロールバーを操作しても何もおこりません
マウスボタンを離せば、つまみは元の位置に戻ってしまいます
これは、私たちがまだ何もメッセージの処理を行っていないためです

スクロールバーのメッセージは、これまでのメッセージと同様に処理することができます
縦スクロールは WM_VSCRLL、横スクロールは WM_HSCROLL です
スクロールバーをクリックされたり、スクロールボックスをドラッグされると発生します

WPARAM には、下位ワードにスクロールバーに
どのような操作を行っているのかを表す定数が格納されています
これを 通知コード と呼び、次の値のいずれかになります

定数解説
SB_BOTTOM 一番下までスクロール
SB_ENDSCROLL スクロール終了
SB_LINEDOWN 1 行下へスクロール
SB_LINEUP 1 行上へスクロール
SB_PAGEDOWN 1 ページ下へスクロール
SB_PAGEUP 1 ページ上へスクロール
SB_LEFT 左端へスクロール
SB_ENDSCROLL スクロール終了
SB_LINELEFT 左へスクロール
SB_LINERIGHT 右へスクロール
SB_PAGELEFT 1 ページ左へスクロール
SB_PAGERIGHT 1 ページ右へスクロール
SB_RIGHT 右端へスクロール
SB_THUMBPOSITION絶対位置へスクロール
SB_THUMBTRACK 指定位置へスクロール ボックスをドラッグ
SB_TOP 一番上までスクロール

これを分析することで、ユーザーがスクロールバーに何をしているのかがわかります

もし、通知コードが SB_THUMBPOSITION または SB_THUMBTRACK であれば
WPARAM の上位ワードにはスクロールバーの位置情報が格納されています
この値は常に、スクロールバーの最小値と最大値 (nMin と nMax) の間です

また LPARAM にはスクロールバーへのハンドルが格納されますが
これはコントロールのスクロールバーの場合です
ウィンドウの一部としてのスクロールバーの場合は無効なので、今回は使いません

このメッセージを処理した場合は 0 を返します
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	static SCROLLINFO scr;
	static TCHAR strScroll[32];

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		scr.cbSize = sizeof(SCROLLINFO);
		scr.fMask = SIF_PAGE | SIF_RANGE;
		scr.nMin = 0;	scr.nMax = 9;
		scr.nPage = 2;

		SetScrollInfo(hwnd , SB_VERT , &scr , TRUE);
		scr.fMask = SIF_POS;
		return 0;
	case WM_VSCROLL:
		switch(LOWORD(wp)) {
		case SB_TOP:
			scr.nPos = scr.nMin;
			break;
		case SB_BOTTOM:
			scr.nPos = scr.nMax;
			break;
		case SB_LINEUP:
			if (scr.nPos) scr.nPos--;
			break;
		case SB_LINEDOWN:
			if (scr.nPos < scr.nMax - 1) scr.nPos++;
			break;
		case SB_PAGEUP:
			scr.nPos -= scr.nPage;
			break;
		case SB_PAGEDOWN:
			scr.nPos += scr.nPage;
			break;
		case SB_THUMBPOSITION:
			scr.nPos = HIWORD(wp);
			break;
		}
		SetScrollInfo(hwnd , SB_VERT , &scr , TRUE);

		wsprintf(strScroll , "Scroll = %d" , scr.nPos);
		InvalidateRect(hwnd , NULL , TRUE);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd , &ps);
		TextOut(hdc , 10 , 10 , strScroll , lstrlen(strScroll));
		EndPaint(hwnd , &ps);
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	MSG msg;
	WNDCLASS winc;

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_VSCROLL ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
	return msg.wParam;
}


スクロールバーを移動させると、ウィンドウにバーの値が表示されます
バーの位置も、常に SetScrollInfo() によって変更されるので
通常のアプリケーションのように、バーが動作しているように思われます

ただし、ウィンドウのサイズなどによってバーのサイズを変更したり
実際にクライアント領域を移動させるにはもう少し複雑なプログラムになります

また、スクロールバーの情報を得るには GetScrollInfo() を使います


SetScrollInfo()

int SetScrollInfo(
	HWND hwnd , int fnBar ,
	LPSCROLLINFO lpsi , BOOL fRedraw
);
スクロールバーの情報を設定します

hwnd - ウィンドウまたはスクロールバーコントロールのハンドルを指定します
fnBar - 対象とするスクロールバーを指定します lpsi - 設定する情報が入った、SCROLLINFO 構造体へのポインタを指定します
fRedraw - 0 以外の値を指定すると再描画され、0 を指定すると再描画されません

戻り値 - スクロールバーの現在のスクロール位置が返ります

fnBar には次の値のいずれかを指定します

定数解説
SB_CTL スクロールバーコントロールの情報を設定します
hwnd は、スクロールバーコントロールのハンドルでなければなりません
SB_HORZ 水平スクロールバーの情報を設定します
hwnd は、ウィンドウのハンドルでなければなりません
SB_VERT 垂直スクロールバーの情報を設定します
hwnd は、ウィンドウのハンドルでなければなりません

GetScrollInfo()

BOOL GetScrollInfo(HWND hwnd , int fnBar , LPSCROLLINFO lpsi);

スクロールバーの情報を取得します

hwnd - ウィンドウまたはスクロールバーコントロールのハンドルを指定します
fnBar - 対象とするスクロールバーを指定します lpsi - 情報を格納する SCROLLINFO 構造体へのポインタを指定します

戻り値 - 成功すれば 0 以外、失敗すれば 0

fnBar には次の値のいずれかを指定します

定数解説
SB_CTL スクロールバーコントロールの情報を設定します
hwnd は、スクロールバーコントロールのハンドルでなければなりません
SB_HORZ 水平スクロールバーの情報を設定します
hwnd は、ウィンドウのハンドルでなければなりません
SB_VERT 垂直スクロールバーの情報を設定します
hwnd は、ウィンドウのハンドルでなければなりません



前のページへ戻る次のページへ