. Win32API)(C言語) - 超初心者向けプログラミング入門
Win32API)(C言語) - 超初心者向けプログラミング入門
Win32API)(C言語) - 超初心者向けプログラミング入門

スクロールバー

SIF_PAGE フラグは、ページ送り時のスクロール量を nPage に設定します。 nPageは現在のクライアント領域ひとつ分の幅/高さに設定します。 (クライアント領域のサイズはWM_SIZEメッセージのLPARAMから取得できます) これによりスクロールボックスのサイズも「全体サイズに対する現在表示中のサイズの割合」の大きさに設定されます。 (ただし操作しづらいので一定の大きさ以下にはなりません)

設定を格納したSCROLLINFO構造体変数を SetScrollInfo 関数に渡すことでスクロールバーに反映させます。 第二引数に SB_HORZ を指定すると水平スクロールバーに、 SB_VERT を指定すると垂直スクロールバーに反映します。

GetScrollInfo関数

GetScrollInfo 関数はスクロールバーの情報を取得します。

BOOL GetScrollInfo( HWND hwnd, int nBar, LPSCROLLINFO lpsi ); ウィンドウhwndのスクロールバーnBarの情報をlpsiに格納する。 成功した場合は0以外を、失敗した場合は0を返す。

SetScrollInfo関数の逆の働きをする関数です。 引数 nBar に指定する定数もSetScrollInfo関数と同じです。

WM_VSCROLLメッセージ

垂直スクロールバーを操作すると WM_VSCROLL メッセージが通知されます。 ウィンドウプロシージャのWPARAMにはスクロールバーの状態が格納されています。

WPARAMの下位ワード( LOWORD マクロで取得)には、ユーザーがスクロールバーに対して行った操作が送られてきます。 (通知コードという) これは以下の定数のいずれかです。

定数 説明 SB_TOP 一番上へスクロール SB_BOTTOM 一番下へスクロール SB_LINEUP 一行上へスクロール (上ボタン) SB_LINEDOWN 一行下へスクロール (下ボタン) SB_PAGEUP 一ページ上へスクロール (スクロールボックスの上側のクリック) SB_PAGEDOWN 一ページ下へスクロール (スクロールボックスの下側のクリック) SB_THUMBTRACK スクロールボックスのドラッグ中 SB_THUMBPOSITION スクロールボックスのドラッグ終了 SB_ENDSCROLL スクロールの終了

WPARAMの上位ワード( HIWORD マクロで取得)には、スクロールバーの現在の位置が格納されています。

LPARAMは使用しません。 ただしスクロールバーはコントロールとして持つこともでき(スクロールバーコントロール)、その場合はコントロールのハンドルが格納されています。

ScrollWindowEx関数

WM_VSCROLL メッセージ内では、ユーザーの操作に対してスクロールバーの状態を更新し、反映させます。 そして ScrollWindowEx 関数を実行して、実際にウィンドウをスクロールさせます。

int ScrollWindowEx( HWND hWnd, int dx, int dy, const RECT *prcScroll, const RECT *prcClip, HRGN hrgnUpdate, LPRECT prcUpdate, UINT flags ); ウィンドウをスクロールする。

第一引数 hWnd はスクロールするウィンドウのハンドルです。

第二、第三引数の dx 、 dy はスクロール量です。 垂直スクロールバーの場合はdx(x軸、水平)は常に0で、dy(y軸、垂直)を設定します。 dyにマイナス値を指定すると上にスクロールします。

第四引数 prcScroll はスクロールする領域の矩形(RECT構造体)です。 NULL を指定するとクライアント領域全体をスクロールします。

第五引数 prcClip はクリッピング領域の矩形(RECT構造体)です。 ここで指定された領域内のみスクロールされます。 この設定は prcScroll よりも優先されます。 必要ない場合は NULL を指定します。

第六引数 hrgnUpdate と第七引数 prcUpdate は、スクロールによって無効になった領域を格納する変数(リージョンハンドル)の指定です。 必要がない場合は NULL を指定します。

第八引数 flags はスクロールを制御するためのフラグです。 以下の定数を指定します。 (複数指定可)

定数 説明 SW_ERASE スクロールにより無効になった領域を消去する。 SW_INVALIDATE と同時指定する。 SW_INVALIDATE スクロールにより発生する無効領域を無効化する。 SW_SCROLLCHILDREN スクロール領域内にある子ウィンドウをスクロールし、 WM_MOVE メッセージを送る。 SW_SMOOTHSCROLL スムーススクロール。 flags の上位ワードでスクロール時間(ミリ秒)を設定する。 定数 説明 ERROR エラー NULLREGION 無効化された領域はない SIMPLEREGION 単一の矩形 COMPLEXREGION 単一の矩形より複雑な形

スクロールにはScrollWindow関数を使用することもできますが、これは互換性のためだけに残されているもので、新しいアプリケーションには使用しないようにマイクロソフトが勧告しています。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < static int lineHeight; //一行の高さ SCROLLINFO si; //スクロールバーの情報 int scrollPosY; //垂直スクロールの位置 switch (message) < case WM_VSCROLL: //垂直スクロールの操作 //スクロールバーの状態の取得 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hWnd, SB_VERT, &si); //スクロールの位置を保存 scrollPosY = si.nPos; //通知コードにより処理を分岐 switch (LOWORD(wParam)) < case SB_TOP: si.nPos = si.nMin; break; case SB_BOTTOM: si.nPos = si.nMax; break; case SB_LINEUP: si.nPos -= lineHeight; //一行上へ break; case SB_LINEDOWN: si.nPos += lineHeight; //一行下へ break; case SB_PAGEUP: si.nPos -= si.nPage; //一ページ上へ break; case SB_PAGEDOWN: si.nPos += si.nPage; //一ページ下へ break; case SB_THUMBTRACK: si.nPos = HIWORD(wParam); break; case SB_THUMBPOSITION: si.nPos = HIWORD(wParam); break; >//いったんスクロールバーに設定を反映してから //スクロール位置を再取得 //Windowsにより位置が調整されて値が変わることがある si.fMask = SIF_POS; SetScrollInfo(hWnd, SB_VERT, &si, TRUE); GetScrollInfo(hWnd, SB_VERT, &si); //先ほど保存しておいた位置と値が異なるならスクロール実行 if (si.nPos != scrollPosY) < //「以前の位置 - 新しい位置」でスクロール量を算出 ScrollWindowEx(hWnd, 0, (scrollPosY - si.nPos), NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE); //ウィンドウを更新 UpdateWindow(hWnd); >break; //以降省略

まず現在のスクロール位置を変数に保存しておきます。 次にWPARAMの下位ワードに送られてくる通知コードを調べ、ユーザーの操作に応じてスクロール位置の増減を行います。 最後に先ほど保存しておいたスクロール位置から値が変更された場合に、ScrollWindowEx関数を実行してスクロール処理を行います。

nPos の値を増加させるとスクロールボックスの位置は下に移動しますが、その場合クライアント領域は上にスクロールしなければなりません。 つまり上から下に文書を読み進める場合はnPosの値は増加させますが、ScrollWindowEx関数に指定するスクロール量の値はマイナス値を指定しなければなりません。 感覚的に逆なので注意してください。

UpdateWindow関数

最後の UpdateWindow 関数はウィンドウにWM_PAINTメッセージを送信します。

BOOL UpdateWindow( HWND hWnd ); ウィンドウhWndに更新領域(無効領域)がある場合にWM_PAINTメッセージを送信する。 成功した場合は0以外を、失敗した場合は0を返す。

この関数は実行しなくても、スクロールにより無効領域が発生するのでWM_PAINTメッセージは送信されます。 しかしWM_PAINTメッセージは送信しても直ちに実行されるとは限らず、処理の優先順位は最も低く設定されています。 つまり他のメッセージの処理待ちがある間は画面は更新されません。 これは画面のちらつきの原因となることがあります。

UpdateWindow関数はメッセージキューではなくウィンドウプロシージャに即座にWM_PAINTメッセージを送信します。 そのため即座に画面は再描画され、ちらつきを抑えることができます。 ただし無効領域が無い状態ではWM_PAINTメッセージは送信されないので、この関数を実行すれば無条件に画面が更新されるというわけではありません。

WM_HSCROLLメッセージ

WM_HSCROLL メッセージは水平スクロールバーの操作時に通知されます。 ここでの処理は WM_VSCROLL メッセージとほぼ同じです。

WPARAMの下位ワードに送られてくる通知コードは以下です。

定数 説明 SB_LEFT 一番左へスクロール SB_RIGHT 一番右へスクロール SB_LINELEFT 一列左へスクロール (左ボタン) SB_LINERIGHT 一列右へスクロール (右ボタン) SB_PAGELEFT 一ページ左へスクロール (スクロールボックスの左側のクリック) SB_PAGERIGHT 一ページ右へスクロール (スクロールボックスの右側のクリック) SB_THUMBTRACK スクロールボックスのドラッグ中 SB_THUMBPOSITION スクロールボックスのドラッグ終了 SB_ENDSCROLL スクロールの終了

定数名が違うだけでWM_VSCROLLメッセージのものと役割は同じです。 実は定数が示す実際の値も同じなので、例えば「SB_LEFT」は「SB_TOP」でも代用できます。 (あまりお勧めしませんが)

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < static int charWidth; //一文字の幅 SCROLLINFO si; //スクロールバーの情報 int scrollPosX; //水平スクロールの位置 switch (message) < case WM_HSCROLL: //水平スクロールの操作 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hWnd, SB_HORZ, &si); scrollPosX = si.nPos; switch (LOWORD(wParam)) < case SB_LEFT: si.nPos = si.nMin; break; case SB_RIGHT: si.nPos = si.nMax; break; case SB_LINELEFT: si.nPos -= charWidth; break; case SB_LINERIGHT: si.nPos += charWidth; break; case SB_PAGELEFT: si.nPos -= si.nPage; break; case SB_PAGERIGHT: si.nPos += si.nPage; break; case SB_THUMBTRACK: si.nPos = HIWORD(wParam); break; case SB_THUMBPOSITION: si.nPos = HIWORD(wParam); break; >si.fMask = SIF_POS; SetScrollInfo(hWnd, SB_HORZ, &si, TRUE); GetScrollInfo(hWnd, SB_HORZ, &si); if (si.nPos != scrollPosX) < ScrollWindowEx(hWnd, (scrollPosX - si.nPos), 0, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE); UpdateWindow(hWnd); >break; //以降省略

水平スクロールバーの場合、ScrollWindowEx関数で指定するのは第二引数( dx )になります。

WM_PAINTメッセージ

ここまでの処理でスクロール自体はできるようになっています。 最後に WM_PAINT メッセージで画面の描画を行います。

//このコードは不完全です #define BUFFERSIZE 64 //ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < //行数 static const int LINES = 30; static int lineHeight; //一行の高さ HDC hdc; PAINTSTRUCT ps; WCHAR buf[BUFFERSIZE]; WCHAR text[] = L"%d行目のテキスト。横になが~~~~~~~~~~~~い"; SCROLLINFO si; //スクロールバーの情報 int scrollPosX; //水平スクロールの位置 int scrollPosY; //垂直スクロールの位置 int drawnLineStart; //描画される行の最初 int drawnLineEnd; //描画される行の最後 int drawnX; //描画位置のX座標 int drawnY; //描画位置のY座標 //途中省略 switch (message) < case WM_PAINT: hdc = BeginPaint(hWnd, &ps); //垂直スクロールの位置取得 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hWnd, SB_VERT, &si); scrollPosY = si.nPos; //水平スクロールの位置取得 GetScrollInfo(hWnd, SB_HORZ, &si); scrollPosX = si.nPos; //ps.rcPaintには無効領域が格納されている //この領域に含まれる行を描画する必要があるので //その行番号を取得する drawnLineStart = (ps.rcPaint.top + scrollPosY) / lineHeight; drawnLineEnd = (ps.rcPaint.bottom + scrollPosY) / lineHeight; //LINES行以上は描画しない if (drawnLineEnd >LINES - 1) drawnLineEnd = LINES - 1; for (int i = drawnLineStart; i EndPaint(hWnd, &ps); break; //以降省略

描画位置はスクロールしている量だけ上や左にズレます。 例えば「下に20、右に10」のスクロールをしている状態だと、スクロール無しの状態よりも「上に20、左に10」の位置から描画を開始します。 そのため、TextOut関数に指定する座標には負数が指定されることもあります。

サンプルコード全体

#include #include #define BUFFERSIZE 64 //ウィンドウの生成等は省略 //ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) < static const int LINES = 30; //行数 static const WCHAR *format = L"%d行目のテキスト。横になが~~~~~~~~~~~~い"; static int charWidth; //一文字の幅 static int lineHeight; //一行の高さ static int scrollWidthMax; //スクロールする最大幅 HDC hdc; PAINTSTRUCT ps; RECT rt; SCROLLINFO si; //スクロールバーの情報 TEXTMETRIC tm; //文字サイズの情報 int scrollPosX; //水平スクロールの位置 int scrollPosY; //垂直スクロールの位置 int drawnLineStart; //描画される行の最初 int drawnLineEnd; //描画される行の最後 int drawnX; //描画位置のX座標 int drawnY; //描画位置のY座標 WCHAR buf[BUFFERSIZE]; switch (message) < case WM_CREATE: //ウィンドウの作成 //デバイスコンテキストの取得 hdc = GetDC(hWnd); //スクロールする最大幅(文字列の幅)を取得 StringCchPrintf(buf, BUFFERSIZE, format, LINES); rt = (RECT)< 0 >; //メンバを0で初期化 DrawText(hdc, buf, -1, &rt, DT_CALCRECT | DT_EXTERNALLEADING); scrollWidthMax = rt.right; //文字サイズに関する情報の取得 GetTextMetrics(hdc, &tm); charWidth = tm.tmAveCharWidth; //文字幅 lineHeight = tm.tmHeight; //一行の高さ //lineHeight = rt.bottom; //これでも良い //デバイスコンテキストの解放 ReleaseDC(hWnd, hdc); break; case WM_SIZE: //ウィンドウサイズの変更 //垂直スクロールバーの設定 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_RANGE | SIF_PAGE; si.nMin = 0; si.nMax = LINES * lineHeight; //一行の高さ * 行数 si.nPage = HIWORD(lParam); //クライアント領域の高さ SetScrollInfo(hWnd, SB_VERT, &si, TRUE); //水平スクロールバーの設定 //si.cbSize = sizeof(SCROLLINFO); //si.fMask = SIF_RANGE | SIF_PAGE; //si.nMin = 0; si.nMax = scrollWidthMax; //文字列の最大幅 si.nPage = LOWORD(lParam); //クライアント領域の幅 SetScrollInfo(hWnd, SB_HORZ, &si, TRUE); break; case WM_VSCROLL: //垂直スクロールの操作 //スクロールバーの状態の取得 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hWnd, SB_VERT, &si); //スクロールの位置を保存 scrollPosY = si.nPos; //通知コードにより処理を分岐 switch (LOWORD(wParam)) < case SB_TOP: si.nPos = si.nMin; break; case SB_BOTTOM: si.nPos = si.nMax; break; case SB_LINEUP: si.nPos -= lineHeight; //一行上へ break; case SB_LINEDOWN: si.nPos += lineHeight; //一行下へ break; case SB_PAGEUP: si.nPos -= si.nPage; //一ページ上へ break; case SB_PAGEDOWN: si.nPos += si.nPage; //一ページ下へ break; case SB_THUMBTRACK: si.nPos = HIWORD(wParam); break; case SB_THUMBPOSITION: si.nPos = HIWORD(wParam); break; >//いったんスクロールバーに設定を反映してから //スクロール位置を再取得 //Windowsにより位置が調整されて値が変わることがある si.fMask = SIF_POS; SetScrollInfo(hWnd, SB_VERT, &si, TRUE); GetScrollInfo(hWnd, SB_VERT, &si); //先ほど保存しておいた位置と値が異なるならスクロール実行 if (si.nPos != scrollPosY) < //「以前の位置 - 新しい位置」でスクロール量を算出 ScrollWindowEx(hWnd, 0, (scrollPosY - si.nPos), NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE); //ウィンドウを更新 UpdateWindow(hWnd); >break; case WM_HSCROLL: //水平スクロールの操作 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hWnd, SB_HORZ, &si); scrollPosX = si.nPos; switch (LOWORD(wParam)) < case SB_LEFT: si.nPos = si.nMin; break; case SB_RIGHT: si.nPos = si.nMax; break; case SB_LINELEFT: si.nPos -= charWidth; break; case SB_LINERIGHT: si.nPos += charWidth; break; case SB_PAGELEFT: si.nPos -= si.nPage; break; case SB_PAGERIGHT: si.nPos += si.nPage; break; case SB_THUMBTRACK: si.nPos = HIWORD(wParam); break; case SB_THUMBPOSITION: si.nPos = HIWORD(wParam); break; >si.fMask = SIF_POS; SetScrollInfo(hWnd, SB_HORZ, &si, TRUE); GetScrollInfo(hWnd, SB_HORZ, &si); if (si.nPos != scrollPosX) < ScrollWindowEx(hWnd, (scrollPosX - si.nPos), 0, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE); UpdateWindow(hWnd); >break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); //垂直スクロールの位置取得 si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hWnd, SB_VERT, &si); scrollPosY = si.nPos; //水平スクロールの位置取得 GetScrollInfo(hWnd, SB_HORZ, &si); scrollPosX = si.nPos; //ps.rcPaintには無効領域が格納されている //この領域に含まれる行を描画する必要があるので //その行番号を取得する drawnLineStart = (ps.rcPaint.top + scrollPosY) / lineHeight; drawnLineEnd = (ps.rcPaint.bottom + scrollPosY) / lineHeight; if (drawnLineEnd > LINES - 1) drawnLineEnd = LINES - 1; for (int i = drawnLineStart; i EndPaint(hWnd, &ps); break; case WM_DESTROY: //ウィンドウの破棄 PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); > return 0; >
📎📎📎📎📎📎📎📎📎📎