スクロールバー
ウィンドウに表示したい情報量が多くなると一枚のウィンドウ上に表示することが難しくなってきます。
スクロールバーを使用すればクライアント領域をスクロールできるようになります。
スクロールバーの表示
ウィンドウにスクロールバーを表示するにはCreateWindow関数の実行時にウィンドウスタイルにWS_VSCROLL
とWS_HSCROLL
フラグを指定します。
WS_VSCROLLは垂直のスクロールバー、WS_HSCROLLは水平のスクロールバーです。
HWND hWnd = CreateWindow(
szWindowClass, szTitle,
WS_OVERLAPPEDWINDOW |
WS_VSCROLL | WS_HSCROLL, //スクロールバーの表示
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
両方のスクロールバーを表示したところです。
ちなみにスクロールバーのつまみ(ドラッグ操作可能なところ)はスクロールボックスと言います。
表示するだけならこれでOKですが、この状態ではスクロールバーは機能しません。
プログラムでスクロールバーの動作を実装する必要があります。
処理は
- スクロール量を
SetScrollInfo
関数とSCROLLINFO
構造体で設定する - スクロールバーを操作すると
WM_VSCROLL
、WM_HSCROLL
メッセージが送信される - このメッセージ内で
ScrollWindowEx
関数を実行し、実際にクライアント領域のスクロール処理を行う
という流れになります。
文字サイズの取得
スクロール処理実装の前に、一文字当たりの幅と高さを取得しておきます。
これはスクロール量を決定するために使用します。
文字サイズ取得はGetTextMetrics関数を使用します。
ついでにDrawText関数にDT_CALCRECT
フラグを指定して、一行の横幅も取得しておきます。
今回は文字サイズや文字列を途中で変更したりはしないので、これらはWM_CREATE
メッセージ内で実行します。
変更する場合はその都度再計算が必要です。
//このコードは不完全です
#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;
RECT rt;
TEXTMETRIC tm; //文字サイズの情報
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);
scrollWidthMax= rt.right;
//文字サイズに関する情報の取得
GetTextMetrics(hdc, &tm);
charWidth = tm.tmAveCharWidth; //一文字の幅
lineHeight = tm.tmHeight; //一行の高さ
//lineHeight = rt.bottom; //これでも良い
//デバイスコンテキストの解放
ReleaseDC(hWnd, hdc);
break;
//以降省略
今回は横に長~いテキストを複数行出力することにします。
SetScrollInfo関数
スクロールバーの状態はSetScrollInfo
関数で設定します。
- int SetScrollInfo(
HWND hwnd,
int nBar,
LPCSCROLLINFO lpsi,
BOOL redraw
); - ウィンドウhwndのスクロールバーnBarをlpsiの状態に設定する。
redrawが真のとき、スクロールバーを再描画する。
戻り値はスクロールボックスの現在の位置。
第一引数hwnd
はウィンドウハンドルです。
ちなみにスクロールバーはウィンドウにも使用されますが、コントロールとしてのスクロールバーも存在します。
スクロールバーコントロールの設定をする場合はウィンドウハンドルではなくコントロールのハンドルを指定します。
第二引数nBar
は設定するスクロールバーの種類を以下の定数のいずれかで指定します。
定数 | 説明 |
---|---|
SB_HORZ | 水平スクロールバー |
SB_VERT | 垂直スクロールバー |
SB_CTL | スクロールバーコントロール |
第三引数lpsi
は必要な情報を設定したSCROLLINFO
構造体変数のポインタを指定します。
第四引数redraw
はTRUE
に設定すると、関数実行後にスクロールバーを再描画します。
SCROLLINFO構造体
SCROLLINFO
構造体は、スクロールバーの状態を格納し、状態の取得/設定に使用します。
- typedef struct tagSCROLLINFO {
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO, *LPSCROLLINFO; - スクロールバーの情報を格納する構造体。
cbSize
メンバはこの構造体のサイズです。
つまりsizeof(SCROLLINFO)
を指定します。
fMask
メンバは取得/設定したい状態を以下の定数で指定します。
指定しなかったものは無視されます。
(複数指定可)
fMask定数 | 説明 |
---|---|
SIF_ALL | すべての状態を取得/設定 ( SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS ) |
SIF_RANGE | スクロールの範囲をnMin とnMax に取得/設定 |
SIF_PAGE | スクロールボックスの長さをnPage に取得/設定 |
SIF_POS | スクロールボックスの位置をnPage に取得/設定 |
SIF_DISABLENOSCROLL | スクロールバーが不要な場合(現在のウィンドウサイズで情報が全て表示可能なとき)、スクロールバーを除去せず無効状態にする (デフォルトでは除去される) (設定のみ) |
SIF_TRACKPOS | ユーザーがスクロールバーをドラッグしているときの位置をnTrackPos に取得(設定はできない) |
nMin
メンバは最小のスクロール位置です。
nMax
メンバは最大のスクロール位置です。
スクロールバーを動かしたとき、値(nPos
)はこの範囲内で変化します。
nPage
メンバはページのサイズです。
つまり一度に表示される領域のサイズです。
ページ単位スクロールで移動する量やスクロールボックスのサイズがこの値により決定されます。
nPos
メンバは現在のスクロール位置(=スクロールボックスの位置)です。
nTrackPos
はユーザーがスクロールボックスをドラッグしているとき、その位置が格納されます。
この構造体が管理するスクロールの値はただの整数値で、何かに関連付けられているわけではありません。
(ピクセル単位などの決まりはない)
この値をどのように扱うかはプログラマの自由です。
今回はクライアント領域のサイズ情報をそのまま使用することにします。
//このコードは不完全です
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//行数
const int LINES = 30;
static int scrollWidthMax;//スクロールする最大幅
static int lineHeight; //一行の高さ
SCROLLINFO si; //スクロールバーの情報
//途中省略
switch (message)
{
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;
//以降省略
ウィンドウのサイズが変更されるとスクロールバーの状態も変更されるため、スクロールバーの設定はWM_SIZEメッセージ内で行います。
WM_SIZEメッセージはウィンドウ生成時にも送られてくるため、初期化も同時に可能です。
SCROLLINFO構造体のfMask
メンバにSIF_RANGE
フラグとSIF_PAGE
フラグを設定します。
SIF_RANGE
フラグは、スクロールの範囲をnMin
とnMax
の間に設定します。
水平スクロールバーでは、nMaxはテキストの最大横幅に設定します。
垂直スクロールバーでは、nMaxは行全体の高さに設定します。
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」でも代用できます。
(あまりお勧めしませんが)
垂直スクロール同じように水平スクロールの位置を増減させ、ScrollWindowEx関数で実際にスクロールさせます。
//ウィンドウプロシージャ
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 <= drawnLineEnd; i++)
{
drawnX = -scrollPosX;
drawnY = lineHeight * i - scrollPosY;
StringCchPrintf(buf, BUFFERSIZE, format, i + 1);
TextOut(hdc, drawnX, drawnY, buf, lstrlen(buf));
}
EndPaint(hWnd, &ps);
break;
//以降省略
新しい関数などは登場しませんが、計算がややこしいので注意してください。
クライアント領域全体を描画するのではなく、(スクロールによって発生した)無効領域に重なる行だけを描画します。
(→無効領域)
クライアント領域外は描画する必要がなく、無効領域でもないので処理を省略できます。
描画位置はスクロールしている量だけ上や左にズレます。
例えば「下に20、右に10」のスクロールをしている状態だと、スクロール無しの状態よりも「上に20、左に10」の位置から描画を開始します。
そのため、TextOut関数に指定する座標には負数が指定されることもあります。
サンプルコード全体
説明で使用したサンプルコードの全体を示します。
#include <windows.h>
#include <strsafe.h>
#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 <= drawnLineEnd; i++)
{
drawnX = -scrollPosX;
drawnY = lineHeight * i - scrollPosY;
StringCchPrintf(buf, BUFFERSIZE, format, i + 1);
TextOut(hdc, drawnX, drawnY, buf, lstrlen(buf));
}
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
実行結果です。
スクロールバーを操作することで全てのテキストを読むことが出来ます。