第294章 ヘッダーコントロールとリストビューの組み合わせ4


今回は、リストビューにスクロールバーが表示されたとき、これを スクロールしてもヘッダコントロールとずれが生じないように改良します。

また、空欄を含むデータファイルを読み込んでも、NULLと表示せず空欄のままに なるようにします。



左の図のように、リストビューをスクロールしてもヘッダコントロールとの ずれはありません。また、3行目の備考欄のように空白は空白として 表示します。



さて、これはどのように実現しているのでしょうか。面倒なのはスクロールの方です。 リストビューが水平スクロールされるとリストビューにWM_HSCROLLメッセージが 届きます。これを捕まえてヘッダコントロールをMoveWindow関数で動かします。 この時ヘッダコントロールの横幅は親ウィンドウのクライアント領域に合わせられているので そのままではヘッダコントロールが切れてしまいます。横方向のサイズをスクロール分だけ 増やしておきます。ScrollWindowEx関数を使うよりは簡単に処理ができます。

さて、問題はスクロールした状態でウィンドウサイズが変更になった場合です。

今までのプログラムではウィンドウサイズが変更になったときは、HDM_LAYOUTメッセージで 適切なヘッダコントロールの位置、大きさを求めてこれに合わせていました。 この時ヘッダコントロールの(0, 0)は親のクライアント領域の(0, 0)に合わせられます。 次にリストビューを(0, nHeaderh)(nHeaderhはヘッダーコントロールの高さ)に移動しました。 リストビューがスクロールされていない状態では、これで大丈夫でした。 しかし、リストビューがスクロールされているとずれてしまいます。

そこで、リストビューのスクロール分を戻してやる必要があります。これには ListView_Scrollマクロが便利です。

では、前置きはこのくらいにしてプログラムを見てみましょう。

// header05.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "新規作成(&N)...", IDM_NEW MENUITEM "開く(&O)...", IDM_OPEN MENUITEM SEPARATOR MENUITEM "名前を付けて保存(&A)...", IDM_SAVEAS MENUITEM "上書き保存(&S)", IDM_SAVE, GRAYED MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END END POPUP "編集(&E)" BEGIN MENUITEM "データの追加(&A)...", IDM_ADDITEM MENUITEM "データの削除(&D)...", IDM_DELITEM MENUITEM "データ編集(&I)...", IDM_EDITITEM END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // ADDITEM DIALOG DISCARDABLE 0, 0, 143, 123 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "データの追加" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,36,7,97,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,36,29,97,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,36,47,29,12,ES_AUTOHSCROLL | ES_NUMBER EDITTEXT IDC_EDIT4,36,63,99,33,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN DEFPUSHBUTTON "OK",IDOK,7,102,50,14 PUSHBUTTON "キャンセル",IDCANCEL,86,102,50,14 LTEXT "氏名:",IDC_STATIC,7,7,16,8 LTEXT "住所:",IDC_STATIC,7,29,16,8 LTEXT "年齢:",IDC_STATIC,7,49,16,8 LTEXT "備考:",IDC_STATIC,7,65,16,8 END

リソース・スクリプトに変更はありません。

// header05.cpp #ifndef STRICT #define STRICT #endif #define ID_HEADER 100 #define ID_LIST 101 #define NO_OF_SUBITEM 4 #define UP 1 #define DOWN 2 #define MAX_FILE_SIZE (1024 * 64) #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <imm.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int CALLBACK MyCompProc(LPARAM, LPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MySetItem(HWND); BOOL MySetListItem(HWND); LRESULT CALLBACK AddItemProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK EditItemProc(HWND, UINT, WPARAM, LPARAM); BOOL MySaveAs(HWND, HWND); BOOL MySave(HWND, HWND); BOOL MyOpen(HWND, HWND); LRESULT CALLBACK MyListProc(HWND, UINT,WPARAM, LPARAM); char szClassName[] = "header05"; //ウィンドウクラス HINSTANCE hInst; int sortsubno[NO_OF_SUBITEM]; HWND g_hList; int no_of_item, param_of_item; int no_of_edititem; //編集するアイテム BOOL bChanged; //内容が変更されたかどうか static char szFile[MAX_PATH], szTitle[MAX_PATH]; char szWinTitle[MAX_PATH], *szOrgTitle = "猫でもわかるヘッダコントロール [%s]"; WNDPROC OrgListProc; //元々のリストビューのプロシージャ

リストビューをサブクラス化したときのプロシージャのプロトタイプ宣言と元々のプロシージャのアドレスを 保存するグローバル変数が増えました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM 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, "猫でもわかるヘッダコントロール", //タイトルバーにこの名前が表示されます 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, nHeaderh, nItem; static HWND hHeader, hList, hEdit; INITCOMMONCONTROLSEX ic; RECT rc; HDLAYOUT hdl; WINDOWPOS wpos; NMHDR *lpnmhdr; NMHEADER *lpnh; char szBuf[256], szName[64]; DWORD dwExStyle; HMENU hMenu; SCROLLINFO si; switch (msg) { case WM_CREATE: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&ic); hHeader = CreateWindow(WC_HEADER, "", WS_CHILD | WS_BORDER | HDS_BUTTONS, 0, 0, 0, 0, hWnd, (HMENU)ID_HEADER, hInst, NULL); hList = CreateWindow(WC_LISTVIEW, "", WS_CHILD | WS_BORDER | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_EDITLABELS, 0, 0, 0, 0, hWnd, (HMENU)ID_LIST, hInst, NULL); //元々のリストビューのプロシージャを保存 OrgListProc = (WNDPROC)GetWindowLong(hList, GWL_WNDPROC); //リストビューのサブクラス化 SetWindowLong(hList, GWL_WNDPROC, (LONG)MyListProc); SetWindowLong(hList, GWL_USERDATA, (LONG)hHeader); dwExStyle = ListView_GetExtendedListViewStyle(hList); dwExStyle |= LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES; ListView_SetExtendedListViewStyle(hList, dwExStyle); g_hList = hList; MySetItem(hHeader); MySetListItem(hList); break; case WM_INITMENU: hMenu = GetMenu(hWnd); if (bChanged && strcmp(szTitle, "") != 0) EnableMenuItem(hMenu, IDM_SAVE, MF_BYCOMMAND | MF_ENABLED); else EnableMenuItem(hMenu, IDM_SAVE, MF_BYCOMMAND | MF_GRAYED); break; case WM_SIZE: rc.left = 0; rc.top = 0; rc.right = LOWORD(lp); rc.bottom = HIWORD(lp); hdl.pwpos = &wpos; hdl.prc = &rc; SendMessage(hHeader, HDM_LAYOUT, 0, (LPARAM)&hdl); SetWindowPos(hHeader, wpos.hwndInsertAfter, wpos.x, wpos.y, wpos.cx, wpos.cy, wpos.flags | SWP_SHOWWINDOW); GetWindowRect(hHeader, &rc); nHeaderh = rc.bottom - rc.top; MoveWindow(hList, 0, nHeaderh, LOWORD(lp), HIWORD(lp) - nHeaderh, TRUE); memset(&si, 0, sizeof(SCROLLINFO)); si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hList, SB_HORZ, &si); ListView_Scroll(hList, -si.nPos, 0); break; case WM_NOTIFY: lpnmhdr = (NMHDR *)lp; if (lpnmhdr->hwndFrom == hHeader) { switch (lpnmhdr->code) { case HDN_ITEMCLICK: lpnh = (NMHEADER *)lp; if (sortsubno[lpnh->iItem] == UP) sortsubno[lpnh->iItem] = DOWN; else sortsubno[lpnh->iItem] = UP; ListView_SortItems(hList, MyCompProc, lpnh->iItem); bChanged = TRUE; break; case HDN_ENDTRACK: lpnh = (NMHEADER *)lp; ListView_SetColumnWidth(hList, lpnh->iItem, lpnh->pitem->cxy); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } } else { return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_ADDITEM: no_of_item = ListView_GetItemCount(hList); if (DialogBox(hInst, "ADDITEM", hWnd, (DLGPROC)AddItemProc) == IDOK) bChanged = TRUE; param_of_item++; break; case IDM_DELITEM: while (1) { nItem = ListView_GetNextItem(hList, -1, LVNI_ALL | LVNI_SELECTED); if (nItem == -1) break; ListView_GetItemText(hList, nItem, 0, szName, sizeof(szName)); wsprintf(szBuf, "「%s」の項目を削除してよろしいですか", szName); id = MessageBox(hWnd, szBuf, "項目の削除", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { ListView_DeleteItem(hList, nItem); bChanged = TRUE; } else //削除しない場合は選択状態を解除しないとまずい(いつまでも聞かれる) ListView_SetItemState(hList, nItem, 0, LVIS_SELECTED); } break; case IDM_EDITITEM: while (1) { nItem = ListView_GetNextItem(hList, -1, LVNI_ALL | LVNI_SELECTED); if (nItem == -1) break; no_of_edititem = nItem; //編集するアイテムNOをプロシージャからもわかるようにする if (DialogBox(hInst, "ADDITEM", hWnd, (DLGPROC)EditItemProc) == IDOK) bChanged = TRUE; } break; case IDM_SAVEAS: MySaveAs(hWnd, hList); wsprintf(szWinTitle, szOrgTitle, szTitle); SetWindowText(hWnd, szWinTitle); break; case IDM_SAVE: MySave(hWnd, hList); break; case IDM_NEW: if (bChanged == TRUE) { id = MessageBox(hWnd, "現在のデータに変更があります。破棄してもよろしいですか", "注意", MB_YESNO | MB_ICONQUESTION); if (id == IDNO) break; } ListView_DeleteAllItems(hList); wsprintf(szWinTitle, szOrgTitle, ""); SetWindowText(hWnd, szWinTitle); break; case IDM_OPEN: if (bChanged == TRUE) { id = MessageBox(hWnd, "現在のデータに変更があります。破棄してもよろしいですか", "注意", MB_YESNO | MB_ICONQUESTION); if (id == IDNO) break; } ListView_DeleteAllItems(hList); wsprintf(szWinTitle, szOrgTitle, ""); SetWindowText(hWnd, szWinTitle); if (MyOpen(hWnd, hList)) { wsprintf(szWinTitle, szOrgTitle, szTitle); SetWindowText(hWnd, szWinTitle); } break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_CLOSE: if (bChanged == TRUE) { id = MessageBox(hWnd, "データに変更があります。破棄してもよろしいですか", "注意", MB_YESNO | MB_ICONQUESTION); if (id == IDNO) break; } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { //サブクラス化の解除 SetWindowLong(hList, GWL_WNDPROC, (LONG)OrgListProc); DestroyWindow(hList); DestroyWindow(hHeader); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

親ウィンドウのプロシージャです。

WM_CREATEメッセージが来た時に、リストビューをサブクラス化しています。 これは、リストビューがユーザーによってスクロールされたのを知るためです。

WM_SIZEメッセージが来たときにGetScrollInfo関数でリストビューのスクロール情報を 取得してListView_Scrollマクロでスクロールしていない状態に戻しています。

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

hwndには、スクロールバーのハンドル、または標準的なスクロールバーを有していてるウィンドウの ハンドルを指定します。どちらを指定するかはfnBarの値によって決まります。

fnBarは、スクロールバーを指定します。次のうちから一つを指定します。

SB_CTLスクロールバーコントロールの情報を取得します。
hwndはスクロールバーコントロールのハンドルを指定します。
SB_HORZウィンドウの標準的水平スクロールバーの情報を取得します。
SB_VERTウィンドウの標準的垂直スクロールバーの情報を取得します。

lpsiにはSCROLLINFO構造体へのポインタを指定します。

この関数を呼び出す前にcbSizeメンバにsizeof(SCROLLINFO)の値をセットしておきます。

また、fMaskメンバに取得したいパラメータを指定しておきます。これは、次の組み合わせで 指定します。

SIF_PAGEスクロールページをnPageメンバにコピーします。
SIF_POSスクロールポジションをnPosメンバにコピーします。
SIF_RANGEスクロールレンジをnMinとnMaxメンバにコピーします。
SIF_TRACKPOS現在のスクロールボックスのトラッキングポジションをnTrackPosにメンバに コピーします。

関数が成功したら0以外、失敗したときは0が返ります。なお、SCROLLINFO構造体については 第100章を参照してください。

さて、nPosメンバの値がわかったらListView_Scrollマクロでリストビューをスクロールしていない 状態に戻します。

BOOL ListView_Scroll( HWND hwnd, int dx, int dy );

hwndにはリストビューのハンドルを指定します。

dxには水平方向のピクセル単位のスクロール量を指定します(大きいアイコン、小さいアイコン、詳細表示)。 リストビューコントロールが一覧表示の時は、カラム数になります。

dyには垂直方向のスクロール量をピクセル単位で指定します。

プログラム終了時にはリストビューのサブクラス化を解除しておきます。

MySetItem, MySetListItem, MyCompProc, AddItemProc, EditItemProc, MySaveAs, MySave の各関数に変更はありません。

BOOL MyOpen(HWND hWnd, HWND hList) { OPENFILENAME ofn; HANDLE hFile; DWORD dwAccBytes, dwSize; char seps[] = "\",\r\n", *token, *lpset; char szBuf[MAX_FILE_SIZE]; int i = 0, j = 0; LVITEM li; param_of_item = 0; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrFile = szFile; ofn.nMaxFile = MAX_PATH; ofn.lpstrFileTitle = szTitle; ofn.nMaxFileTitle = MAX_PATH; ofn.hwndOwner = hWnd; ofn.lpstrFilter = "*.lsv\0*.lsv\0All Files\0*.*\0\0"; ofn.lpstrDefExt = "*.lsv"; ofn.lpstrTitle = "データファイルを開く"; if (GetOpenFileName(&ofn) == 0) return FALSE; hFile = CreateFile(szFile, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "ファイルをオープンできません", "Error", MB_OK); return FALSE; } dwSize = GetFileSize(hFile, NULL); if (dwSize > MAX_FILE_SIZE) { MessageBox(hWnd, "ファイルサイズが大きすぎます", "Error", MB_OK); CloseHandle(hFile); return FALSE; } ReadFile(hFile, szBuf, dwSize, &dwAccBytes, NULL); szBuf[dwAccBytes] = '\0'; CloseHandle(hFile); token = strtok(szBuf, seps); while (token != NULL) { memset(&li, 0, sizeof(LVITEM)); if (j == 0) li.mask = LVIF_TEXT | LVIF_PARAM; else li.mask = LVIF_TEXT; li.iItem = i; li.iSubItem = j; lpset = strstr(token, "&r"); while (lpset != NULL) { token[lpset - token] = '\r'; token[lpset - token + 1] = '\n'; lpset =strstr(token, "&r"); } if (strcmp(token, "NULL") != 0) li.pszText = token; else li.pszText = ""; li.cchTextMax = strlen(li.pszText); if (j == 0) { li.lParam = param_of_item; ListView_InsertItem(hList,&li); } else ListView_SetItem(hList, &li); j++; if (j > 3) { j = 0; i++; } param_of_item++; token = strtok(NULL, seps); } bChanged = FALSE; return TRUE; }

ファイルを開いて、リストビューに表示する関数ですが、NULLのデータは リストビューには何も表示しないようにしました。

//サブクラス化されたリストビューのプロシージャ //デフォルトの動作をじゃましないようすべてのメッセージはCallWindowProcに返す LRESULT CALLBACK MyListProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { SCROLLINFO si; RECT rc; static HWND hHeader; switch (msg) { case WM_HSCROLL: hHeader = (HWND)GetWindowLong(hWnd, GWL_USERDATA); si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hWnd, SB_HORZ, &si); GetWindowRect(hHeader, &rc); MoveWindow(hHeader, -1 * si.nPos, 0, rc.right - rc.left + si.nPos, rc.bottom - rc.top, TRUE); break; default: break; } return CallWindowProc(OrgListProc, hWnd, msg, wp, lp); }

サブクラス化したリストビューのプロシージャです。

WM_HSCROLLメッセージが来たらヘッダーコントロールをMoveWindow関数で位置と大きさを変更しています。 ちょっと邪道です。(が、簡単です)

さて、ヘッダコントロールのハンドルの取得の仕方を見てください。「おや?」と思われるかもしれません。 これは、親ウィンドウのプロシージャの中でWM_CREATEメッセージが来たときの処理を見てください。

SetWindowLong(hList, GWL_USERDATA, (LONG)hHeader);

というのがあると思いますが、ここでちゃっかりユーザーデータにヘッダコントロールのハンドルを コピーしています。各ウィンドウには関連づけられた32ビット値を持っていてウィンドウを作った アプリケーションが自由に使うことができます。(こういう場合普通の人はヘッダコントロールの ハンドルをグローバル変数にすると思いますが・・・・)

あと、オプションでヘッダーコントロールやリストビューのフォントを変更できるとよいですね。 改良してみてください。


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

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