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


前章までで作ったものは、せっかくデータを入力しても プログラムを終了すると、消えてしまいました。入力したデータを保存したり、保存してある データを読み出したりする機能は是非とも欲しいものです。なおかつ、保存したデータは エクセルなどの市販のアプリケーションでも読み出せれば申し分ありませんね。



では、保存の方法を考えてみましょう。

これは、簡単です。それぞれのアイテムやサブアイテムを読み出して、 テキストで保存しておきます。この時

"アイテム1","サブアイテム1−1","サブアイテム1−2","サブアイテム1−3",
"アイテム2","サブアイテム2−1","サブアイテム2−2","サブアイテム2−3",
....

というような感じで保存されているとエクセルで読み出すことができます。

さて、そこで問題があります。前回までで作ったプログラムでは、「備考」欄は 改行を含む文字列が格納されている可能性があります。そのまま、上のような形式で 保存すると改行のあるところからずーっとデータがずれてしまいます。そこで 保存するときには改行があるときはこれを別な文字に置き換えることにします。 ここでは「&r」で置き換えます。(保存したファイルを「エクセル」などで読み込むと 改行の所にはそのまま「&r」という文字が表示されてしまいますが、データのずれはなくなります。) ファイルを読み込むときは、「&r」が出てきたら「\r\n」に置き換えます。

では、プログラムを見てみましょう。

// header04.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

メニューに「新規作成」「開く」「名前を付けて保存」「上書き保存」などが増えました。

// header04.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); char szClassName[] = "header04"; //ウィンドウクラス 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]";

コモンコントロールやIMEの前準備を忘れないでください。

MAX_FILE_SIZEを64キロと定義しています。(後述)
MySaveAs, MySave, MyOpenなどの自作関数のプロトタイプ宣言が増えました。

グローバル変数のbChangedは、データの追加、削除、編集などでデータが 変化したらTRUEにして、名前を付けて保存したり、上書き保存したときに FALSEにしておきます。これがTRUEなのにユーザーがアプリケーションを 終了しようとしたときなどに注意を促します。

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; 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); 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); 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) { DestroyWindow(hList); DestroyWindow(hHeader); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

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

WM_INITMENUメッセージが来たら、bChangedがTRUEかつ、ウィンドウタイトルが ヌルではない(名前を付けて保存されている)場合には、メニューの「上書き保存」を 使用可能にし、そうでないときは使用不能にしています。

WM_NOTIFYメッセージが来て、アイテムの並び替えが起こったときはbChangedをTRUEにします。

メニューから「データの編集」が選択されて、ダイアログボックスがIDOKを返したときは bChangedをTRUEにします。

メニューから「データの削除」「データの追加」が選択されて、これが実行されたときもbChangedを TRUEにします。

メニューからIDM_SAVEASが選択されたら自作関数MySaveAsを呼び出して、タイトルバーに ファイル名を表示します。

メニューからIDM_SAVEが選択されたら自作関数MySaveを呼び出します。

メニューからIDM_NEWが選択された時、bChangedがTRUEであれば注意を促します。
そして、ListView_DeleteAllItemsマクロで(第221章参照)、 すべてのアイテムを削除して、タイトルバーの ファイル名をヌルにしておきます。

メニューからIDM_OPENが選択された時、bChangedがTRUEであれば注意を促します。
そして、すべてのアイテムをデリートして自作関数MyOpenを呼び出します。

プログラム終了時にbChangedがTRUEであれば、注意を促します。

BOOL MySetItem(HWND hHeader) { HDITEM hi; hi.mask = HDI_FORMAT | HDI_TEXT | HDI_WIDTH; hi.pszText = "名 前"; hi.cxy = 100; hi.fmt = HDF_CENTER | HDF_STRING; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 0, (LPARAM)&hi); hi.pszText = "住所"; hi.cxy = 200; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 1, (LPARAM)&hi); hi.pszText = "年齢"; hi.cxy = 60; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 2, (LPARAM)&hi); hi.pszText = "備 考"; hi.cxy = 260; hi.cchTextMax = strlen(hi.pszText); SendMessage(hHeader, HDM_INSERTITEM, 3, (LPARAM)&hi); return TRUE; } BOOL MySetListItem(HWND hList) { LVCOLUMN lc; lc.mask = LVCF_WIDTH | LVCF_SUBITEM; lc.cx = 100; lc.iSubItem = 0; ListView_InsertColumn(hList, 0, &lc); lc.cx = 200; lc.iSubItem = 1; ListView_InsertColumn(hList, 1, &lc); lc.cx = 60; lc.iSubItem = 2; ListView_InsertColumn(hList, 2, &lc); lc.cx = 260; lc.iSubItem = 3; ListView_InsertColumn(hList, 3, &lc); return TRUE; } // アプリケーション定義比較関数 int CALLBACK MyCompProc(LPARAM lp1, LPARAM lp2, LPARAM lp3) { static LVFINDINFO lvf; static int nItem1, nItem2; static char buf1[256], buf2[256]; lvf.flags = LVFI_PARAM; lvf.lParam = lp1; nItem1 = ListView_FindItem(g_hList, -1, &lvf); lvf.lParam = lp2; nItem2 = ListView_FindItem(g_hList, -1, &lvf); ListView_GetItemText(g_hList, nItem1, (int)lp3, buf1, sizeof(buf1)); ListView_GetItemText(g_hList, nItem2, (int)lp3, buf2, sizeof(buf2)); if (lp3 != 2) { if (sortsubno[(int)lp3] == UP) return(strcmp(buf1, buf2)); else return(strcmp(buf1, buf2) * -1); } else { if (sortsubno[(int)lp3] == UP) { //年齢の時は数値で比較 return (atoi(buf1) - atoi(buf2)); //buf1, buf2が数字でないときはatoiは0を返す } else { return ((atoi(buf1) - atoi(buf2)) * -1); } } } LRESULT CALLBACK AddItemProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { char szName[64], szAddress[256], szAge[8], szMemo[512]; LVITEM li; HIMC hImc; static HWND hName, hAddress, hAge, hMemo; switch (msg) { case WM_INITDIALOG: hName = GetDlgItem(hDlg, IDC_EDIT1); hAddress = GetDlgItem(hDlg, IDC_EDIT2); hAge = GetDlgItem(hDlg, IDC_EDIT3); hMemo = GetDlgItem(hDlg, IDC_EDIT4); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hName, szName, sizeof(szName)); Edit_GetText(hAddress, szAddress, sizeof(szAddress)); Edit_GetText(hAge, szAge, sizeof(szAge)); Edit_GetText(hMemo, szMemo, sizeof(szMemo)); li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = szName; li.iItem = no_of_item; li.lParam = param_of_item; li.iSubItem = 0; ListView_InsertItem(g_hList, &li); li.mask = LVIF_TEXT; li.pszText = szAddress; li.iItem = no_of_item ; li.iSubItem = 1; ListView_SetItem(g_hList, &li); li.pszText = szAge; li.iSubItem = 2; ListView_SetItem(g_hList, &li); li.pszText = szMemo; li.iSubItem = 3; ListView_SetItem(g_hList, &li); no_of_item++; EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; case IDC_EDIT1: case IDC_EDIT2: case IDC_EDIT4: if (HIWORD(wp) == EN_SETFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, TRUE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } if (HIWORD(wp) == EN_KILLFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, FALSE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } return FALSE; } return FALSE; } return FALSE; } LRESULT CALLBACK EditItemProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { char szName[64], szAddress[256], szAge[8], szMemo[512], szTitle[64]; LVITEM li_get, li; static HWND hName, hAddress, hAge, hMemo; HIMC hImc; switch (msg) { case WM_INITDIALOG: hName = GetDlgItem(hDlg, IDC_EDIT1); hAddress = GetDlgItem(hDlg, IDC_EDIT2); hAge = GetDlgItem(hDlg, IDC_EDIT3); hMemo = GetDlgItem(hDlg, IDC_EDIT4); ListView_GetItemText(g_hList, no_of_edititem, 0, szName, sizeof(szName)); ListView_GetItemText(g_hList, no_of_edititem, 1, szAddress, sizeof(szAddress)); ListView_GetItemText(g_hList, no_of_edititem, 2, szAge, sizeof(szAge)); ListView_GetItemText(g_hList, no_of_edititem, 3, szMemo, sizeof(szMemo)); Edit_SetText(hName, szName); Edit_SetText(hAddress, szAddress); Edit_SetText(hAge, szAge); Edit_SetText(hMemo, szMemo); wsprintf(szTitle, "「%s」の編集", szName); SetWindowText(hDlg, szTitle); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hName, szName, sizeof(szName)); Edit_GetText(hAddress, szAddress, sizeof(szAddress)); Edit_GetText(hAge, szAge, sizeof(szAge)); Edit_GetText(hMemo, szMemo, sizeof(szMemo)); li_get.mask = LVIF_PARAM; li_get.iItem = no_of_edititem; li_get.iSubItem = 0; ListView_GetItem(g_hList, &li_get); li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = szName; li.iItem = no_of_edititem; li.lParam = li_get.lParam; li.iSubItem = 0; ListView_SetItem(g_hList, &li); li.mask = LVIF_TEXT; li.pszText = szAddress; li.iItem = no_of_edititem ; li.iSubItem = 1; ListView_SetItem(g_hList, &li); li.pszText = szAge; li.iSubItem = 2; ListView_SetItem(g_hList, &li); li.pszText = szMemo; li.iSubItem = 3; ListView_SetItem(g_hList, &li); ListView_SetItemState(g_hList, no_of_edititem, 0, LVIS_SELECTED); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: ListView_SetItemState(g_hList, no_of_edititem, 0, LVIS_SELECTED); EndDialog(hDlg, IDCANCEL); return TRUE; case IDC_EDIT1: case IDC_EDIT2: case IDC_EDIT4: if (HIWORD(wp) == EN_SETFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, TRUE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } if (HIWORD(wp) == EN_KILLFOCUS) { hImc = ImmGetContext((HWND)lp); ImmSetOpenStatus(hImc, FALSE); ImmReleaseContext((HWND)lp, hImc); return TRUE; } return FALSE; } return FALSE; } return FALSE; }

ここは、変更はありません。

BOOL MySaveAs(HWND hWnd, HWND hList) { int nCount, i; OPENFILENAME ofn; HANDLE hFile; char szBuf[1024], szName[64], szAddress[256], szAge[8], szMemo[512], *lpset; DWORD dwWritten; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.lpstrFile = szFile; ofn.nMaxFile = MAX_PATH; ofn.lpstrFileTitle = szTitle; ofn.nMaxFileTitle = MAX_PATH; ofn.lpstrFilter = "*.lsv\0*.lsv\0All Files\0*.*\0\0"; ofn.lpstrDefExt = "*.lsv"; ofn.hwndOwner = hWnd; ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; ofn.lpstrTitle = "名前を付けて保存"; if (GetSaveFileName(&ofn) == 0) return FALSE; hFile = CreateFile(szFile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "ファイルをオープンできません", "Error", MB_ICONEXCLAMATION | MB_OK); return FALSE; } nCount = ListView_GetItemCount(hList); for (i = 0; i < nCount; i++) { ListView_GetItemText(hList, i, 0, szName, sizeof(szName)); if (strcmp(szName, "") == 0) strcpy(szName, "NULL"); ListView_GetItemText(hList, i, 1, szAddress, sizeof(szAddress)); if (strcmp(szAddress, "") == 0) strcpy(szAddress, "NULL"); ListView_GetItemText(hList, i, 2, szAge, sizeof(szAge)); if (strcmp(szAge, "") == 0) strcpy(szAge, "NULL"); ListView_GetItemText(hList, i, 3, szMemo, sizeof(szMemo)); if (strcmp(szMemo, "") == 0) strcpy(szMemo, "NULL"); lpset = strstr(szMemo, "\r\n"); while (lpset != NULL) { szMemo[lpset - szMemo] = '&'; szMemo[lpset - szMemo + 1] = 'r'; lpset = strstr(szMemo, "\r\n"); } wsprintf(szBuf, "\"%s\",\"%s\",\"%s\",\"%s\"\r\n", szName, szAddress, szAge, szMemo); WriteFile(hFile, szBuf, strlen(szBuf) , &dwWritten, NULL); } CloseHandle(hFile); bChanged = FALSE; return TRUE; }

名前を付けて保存する関数です。

OPENFILENAME構造体とGetSaveFileName関数で、保存するファイルのフルパス付きの名前とパスなしの名前を 取得します。
次にこの名前で、書き込みモードでファイルをオープンします。
アイテムの数を調べて(ListView_GetItemCountマクロ(第199章参照)) その数だけ次の作業を繰り返します。
サブアイテムの0番(アイテム)から3番までのテキストを取得 (ListView_GetItemTextマクロ(第110章参照))し、 szName, szAddress, szAge, szMemoに格納します。

もし、何も記載がない場合は「NULL」という文字列を格納します。

サブアイテムの3番については、もし取得した文字列に改行(\r\n)文字があればこれを「&r」に置き換えます。

そして、wsprintf関数でダブルクォーテーションとカンマで整形して、ファイルに書き込みます。

すべてのデータを書き込んだら、ファイルをクローズしてbChangedをFALSEにしておきます。

BOOL MySave(HWND hWnd, HWND hList) { HANDLE hFile; char szBuf[1024], szName[64], szAddress[256], szAge[8], szMemo[512], *lpset; DWORD dwWritten; int nCount, i; if (!bChanged) { MessageBox(hWnd, "変更はありません", "Error", MB_OK | MB_ICONEXCLAMATION); return FALSE; } hFile = CreateFile(szFile, GENERIC_WRITE, 0, 0, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "ファイルをオープンできません", "Error", MB_ICONEXCLAMATION | MB_OK); return FALSE; } nCount = ListView_GetItemCount(hList); for (i = 0; i < nCount; i++) { ListView_GetItemText(hList, i, 0, szName, sizeof(szName)); if (strcmp(szName, "") == 0) strcpy(szName, "NULL"); ListView_GetItemText(hList, i, 1, szAddress, sizeof(szAddress)); if (strcmp(szAddress, "") == 0) strcpy(szAddress, "NULL"); ListView_GetItemText(hList, i, 2, szAge, sizeof(szAge)); if (strcmp(szAge, "") == 0) strcpy(szAge, "NULL"); ListView_GetItemText(hList, i, 3, szMemo, sizeof(szMemo)); if (strcmp(szMemo, "") == 0) strcpy(szMemo, "NULL"); lpset = strstr(szMemo, "\r\n"); while (lpset != NULL) { szMemo[lpset - szMemo] = '&'; szMemo[lpset - szMemo + 1] = 'r'; lpset = strstr(szMemo, "\r\n"); } wsprintf(szBuf, "\"%s\",\"%s\",\"%s\",\"%s\"\r\n", szName, szAddress, szAge, szMemo); WriteFile(hFile, szBuf, strlen(szBuf) , &dwWritten, NULL); } CloseHandle(hFile); bChanged = FALSE; return TRUE; }

上書き保存の関数です。MySaveAs関数とほとんど同じです。ファイル名をユーザーに入力させずに 現在のファイル名をそのまま使っているだけです。ちょっと工夫すると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 - 1) { 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"); } li.pszText = token; 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; }

ファイルを開く関数です。

まず、GetOpenFileName関数で開くファイルの名前を取得します。

ファイルをオープンしたら、中身を一気にszBufに読み込みます。szBufは サイズがMAX_FILE_SIZEなので、ファイルサイズがこれ以上大きなものは 読めません。メモリを動的にdwSizeだけ確保したらよいのでは、と思われるかもしれませんが ちょっと問題があります。

次に、このszBufをダブるクォーテーションと改行文字で切り分けていきます。 strtok関数を使うのが簡単なので、ここでもこれを利用しています。 strtok関数を使うためszBufが動的に確保したものでは危険があります。

トークンがなくなるまで、次の作業を繰り返します。

iはアイテム番号、jはサブアイテムの番号です。
jが0ならばアイテムなのでLVITEM構造体のmaskメンバをLVIF_TEXT | LVIF_PARAMに設定します。
0でなければサブアイテムなのでLVIF_TEXTに設定します。

iItemメンバはi, iSubItemメンバはjに設定します。

切り出したトークンに「&r」が存在すればこれを「\r\n」に置き換えます。

pszTextメンバにトークンを指定します。

jが0ならばアイテムなのでlParamメンバをparam_of_itemに設定します。そして ListView_InsertItemマクロでアイテムを挿入します。

jが0でないならば、サブアイテムなのでListView_SetItemマクロでセットします。

jの値を一つ増やします。もし、3より大きくなった時は、jを0にしてiを1増やします。

これをトークンがヌルになるまで繰り返すと、ファイの中身がリストビューに表示されます。 特にアイテムのlParamメンバの値は、各アイテムに対して一意でないと並べ替えの時 おかしな結果になるので注意してください。

さて、サブアイテムなどに空欄があるとファイルには「NULL」と書き込まれました。 これを、読み込むとそのまま「NULL」と表示されてしまいます。これを改善してみてください。 さらに、ウィンドウを小さくするとリストビューに自動的にスクロールバーが表示されます。 これを左右にスクロールさせると、リストビューとヘッダーコントロールがずれてしまい、 大変みっともないことになります。(もっとも、ヘッダーコントロールとリストビューを 組み合わせず素直にリストビューだけでプログラムを組んでいれば、こんなことはおきませんが・・) このへんを改良してみてください。


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

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