第222章 ディレクトリの内容をソートする


さて、今回はディレクトリに内容をソートすることにします。 ローカルの表示を「ファイル名」でソートした場合FTPの表示も 「ファイル名」でソートします。



さて、リストビューをソートする方法は 第110章で解説してあります。 さて、そこのアプリケーション定義比較関数を良くみてください。 この関数内で必要な情報はソートするリストビューのハンドルと どのサブアイテムを基準にソートするのか、昇順か降順かの 3つです。また、この関数にアプリケーションから渡せるデータは 最後の引数のlp3のみです。第110章ではlp3に基準となるサブアイテムの インデックスを渡しています。あと残りの情報はグローバル変数から 取得しています。今回作成しているプログラムはなるべくグローバル変数を 使わないようにしているので、どうしたらよいのでしょうか。

答えは簡単ですね。必要な情報をメンバに持つ構造体を作って、このアドレスを 渡してやればよいですね。これでグローバル変数を増やすことなく目的を 達成できそうです。

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

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

// myftp06.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <wininet.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #define ID_LIST 100 #define ID_STATIC 101 #define ID_LISTL 102 #define ID_STATICL 103 #define UP 1 //ソート;昇順 #define DOWN 2 //降順 #define NO_OF_SUBITEM 3 //サブアイテムの数 typedef struct _tagAccount{ char szUserName[64]; char szPassWord[64]; } ACCOUNT; typedef struct _tagFTPAddress{ char szHost[64]; char szBaseDir[64]; } FTPADDRESS; typedef struct _tagINETHANDLE{ HINTERNET hInternet; HINTERNET hHost; } INETHANDLE; typedef struct _tagFNAME{ char szFName[MAX_PATH]; char szLocalFileName[MAX_PATH]; } FNAME; typedef struct _tagHWNDSET{ HWND hwnd1; HWND hwnd2; } HWNDSET; typedef struct _tagSORTDATA{ HWND hwndList; //リストビューのhwnd int isortSubItem; //ソートするサブアイテムインデックス int iUPDOWN; //昇順か降順か } SORTDATA; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyAccountProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyFtpAddressProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyGetFNameProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDriveProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyCreateDirProc(HWND, UINT, WPARAM, LPARAM); int CALLBACK MyCompProc(LPARAM, LPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MyConnect(HWND, INETHANDLE *, ACCOUNT, FTPADDRESS, HWND, HWND); void MyDown(HWND, INETHANDLE, FTPADDRESS, FNAME *, HWND, HWND, HWND); void SetAccount(HWND, ACCOUNT *); void SetHost(HWND, FTPADDRESS *); void GetAllFiles(HWND, HWND, HWND, INETHANDLE *); void SetMyLocalDir(HWND, HWND, HWND); void ChangeLocalDrive(HWND, HWND, HWND); void MyUp(HWND, INETHANDLE, HWND, HWND, HWND, HWND); void MyFtpDelFile(HWND, INETHANDLE, HWND, HWND); void MyFtpCreatDir(HWND, INETHANDLE, HWND, HWND); HWND MakeMyList(HWND); void InsertMyColumn(HWND); const char szWindowTitle[] = "猫でもわかるFTP"; char szClassName[] = "myftp06"; //ウィンドウクラス BOOL bConnect = FALSE;//インターネットに接続しているかどうか

UP, DOWN,NO_OF_SUBITEMをそれぞれ1,2,3に定義しました。
SORTDATA構造体をtypedefしました。この構造体のメンバを 設定してアドレスをアプリケーション定義比較関数 (MyCompProc)に渡します。

WinMain, InitApp, InitInstanceの各関数に変更はありません。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, i, iIndex; static ACCOUNT myaccount; static FNAME myfname; static FTPADDRESS myftpaddress; static INETHANDLE inet; static HWND hList, hStatic, hListL, hStaticL; static HINSTANCE hInst; RECT rc; HDWP hDwp; char szDir[MAX_PATH], szBuf[MAX_PATH]; INITCOMMONCONTROLSEX icc; LPNMHDR lpnmhdr; static int sortsubno[NO_OF_SUBITEM] = {UP}; NMLISTVIEW *pNMLV; SORTDATA SortData; static int isortsubno;//どのサブアイテムでソートされているか switch (msg) { case WM_CREATE: icc.dwSize = sizeof(INITCOMMONCONTROLSEX); icc.dwICC = ICC_LISTVIEW_CLASSES; InitCommonControlsEx(&icc); hInst = ((LPCREATESTRUCT)lp)->hInstance; GetClientRect(hWnd, &rc); hList = MakeMyList(hWnd); InsertMyColumn(hList); hListL = MakeMyList(hWnd); InsertMyColumn(hListL); hStatic = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0, 0, 0, hWnd, (HMENU)ID_STATIC, hInst, NULL); hStaticL = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0, 0, 0, hWnd, (HMENU)ID_STATICL, hInst, NULL); SetMyLocalDir(hWnd, hListL, hStaticL); SortData.hwndList = hListL; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hListL, MyCompProc, &SortData); break; case WM_SIZE: hDwp = BeginDeferWindowPos(4); hDwp = DeferWindowPos(hDwp, hStaticL, HWND_TOP, 10, 5, LOWORD(lp) / 2 - 15, 30, SWP_SHOWWINDOW); hDwp = DeferWindowPos(hDwp, hStatic, HWND_TOP, LOWORD(lp) / 2 + 5, 5, LOWORD(lp) / 2 - 15, 30, SWP_SHOWWINDOW); hDwp = DeferWindowPos(hDwp, hListL, HWND_TOP, 10, 40, LOWORD(lp) / 2 - 15, HIWORD(lp) - 50, SWP_SHOWWINDOW); hDwp = DeferWindowPos(hDwp, hList, HWND_TOP, LOWORD(lp) / 2 + 5, 40, LOWORD(lp) / 2 - 15, HIWORD(lp) - 50, SWP_SHOWWINDOW); EndDeferWindowPos(hDwp); break; case WM_NOTIFY: lpnmhdr = (LPNMHDR)lp; if (lpnmhdr->hwndFrom == hListL) { switch (lpnmhdr->code) { case NM_DBLCLK: iIndex = ListView_GetNextItem(hListL, -1, LVNI_SELECTED); ListView_GetItemText(hListL, iIndex, 0, szDir, sizeof(szDir)); if (strstr(szDir, "<dir> ") != szDir) { MessageBox(hWnd, "ディレクトリではありません", "注意", MB_OK); break; } for (i =0; i <= lstrlen(szDir) - 5; i++) { szBuf[i] = szDir[i + 6]; } SetCurrentDirectory(szBuf); SetMyLocalDir(hWnd, hListL, hStaticL); SortData.hwndList = hListL; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hListL, MyCompProc, &SortData); break; case LVN_COLUMNCLICK: pNMLV = (NMLISTVIEW *)lp; isortsubno = pNMLV->iSubItem; if (sortsubno[isortsubno] == UP) sortsubno[isortsubno] = DOWN; else sortsubno[isortsubno] = UP; SortData.hwndList = hListL; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; if(ListView_SortItems(hListL, MyCompProc, &SortData) != TRUE) MessageBox(hWnd, "ソート失敗", "Error", MB_OK); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } } if (lpnmhdr->hwndFrom == hList) { switch (lpnmhdr->code) { case NM_DBLCLK: iIndex = ListView_GetNextItem(hList, -1, LVNI_SELECTED); ListView_GetItemText(hList, iIndex, 0, szDir, sizeof(szDir)); if (strstr(szDir, "<dir> ") != szDir) { MessageBox(hWnd, "ディレクトリではありません", "注意", MB_OK); break; } for (i =0; i <= lstrlen(szDir) - 5; i++) { szBuf[i] = szDir[i + 6]; } FtpSetCurrentDirectory(inet.hHost, szBuf); GetAllFiles(hWnd, hList, hStatic, &inet); SortData.hwndList = hList; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hList, MyCompProc, &SortData); break; case LVN_COLUMNCLICK: pNMLV = (NMLISTVIEW *)lp; isortsubno = pNMLV->iSubItem; if (sortsubno[isortsubno] == UP) sortsubno[isortsubno] = DOWN; else sortsubno[isortsubno] = UP; SortData.hwndList = hList; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; if(ListView_SortItems(hList, MyCompProc, &SortData) != TRUE) MessageBox(hWnd, "ソート失敗", "Error", MB_OK); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_LOCALDRIVE: ChangeLocalDrive(hWnd, hListL, hStaticL); SortData.hwndList = hListL; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hListL, MyCompProc, &SortData); break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_CONNECT: bConnect = MyConnect(hWnd, &inet, myaccount, myftpaddress, hList, hStatic); SortData.hwndList = hList; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hList, MyCompProc, &SortData); break; case IDM_DOWNLOAD: MyDown(hWnd, inet, myftpaddress, &myfname, hList, hListL, hStaticL); SortData.hwndList = hListL; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hListL, MyCompProc, &SortData); break; case IDM_UPLOAD: MyUp(hWnd, inet, hList, hStatic, hListL, hStaticL); SortData.hwndList = hList; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hList, MyCompProc, &SortData); break; case IDM_DELFILE: MyFtpDelFile(hWnd, inet, hList, hStatic); SortData.hwndList = hList; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hList, MyCompProc, &SortData); break; case IDM_CREATEDIR: MyFtpCreatDir(hWnd, inet, hList, hStatic); SortData.hwndList = hList; SortData.isortSubItem = isortsubno; SortData.iUPDOWN = sortsubno[isortsubno]; ListView_SortItems(hList, MyCompProc, &SortData); break; case IDM_ACCOUNT: SetAccount(hWnd, &myaccount); break; case IDM_GETHOST: SetHost(hWnd, &myftpaddress); break; case IDM_DISCONNECT: if (!bConnect) { MessageBox(hWnd, "インターネットに接続されていません", "失敗", MB_OK); break; } if (InternetCloseHandle(inet.hHost)) MessageBox(hWnd, "FTPをクローズしました", "OK", MB_OK); if (InternetCloseHandle(inet.hInternet)) MessageBox(hWnd, "インターネットから切り離しました", "OK", MB_OK); bConnect = FALSE; SetWindowText(hWnd, szWindowTitle); break; } break; case WM_CLOSE: if (bConnect) { MessageBox(hWnd, "インターネットの接続を切ってから終了してください", "注意", MB_OK); break; } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hList); DestroyWindow(hStatic); DestroyWindow(hListL); DestroyWindow(hStaticL); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

新たにsortsubno[NO_OF_SUBITEM]を宣言しています。もしサブアイテムのインデックスが 1番を基準として降順ならsortsubno[1] = UPとなります。

WM_CREATEが来たらSetMyLocalDir関数でローカルのディレクトリを表示後、インデックス0 のサブアイテム(ファイル名)を基準に昇順にソートします。

ソートするにはSORTDATA構造体にリストビューのハンドル、基準となるサブアイテムのインデックス、 昇順か降順かのデータをセットして、ListView_SortItemsマクロを呼び出します。 このマクロの最後の引数に構造体のアドレスを渡します。これで必要なデータがすべて アプリケーション定義比較関数に渡されるはずです。

ローカルのディレクトリを表示するリストビューから通知メッセージがきて、それが ダブルクリックであればカレントディレクトリを変更して 新しいディレクトリを表示後、ソートを行います。ダブルクリックされたのが FTP側の時もも同じ処理をします。

列ボタンが押されたとき(LVN_COLUMNCLICK)はisortsubnoにクリックされた列のインデックス を保存します。その列が今まで昇順であったら降順に、降順であったら昇順になるように sortsubno[列インデックス]を変更しておきます。その後ソートを実行します。 この処理はローカル、FTPのリストビューで同じです。

メニューの「ドライブの変更」(IDM_LOCALDRIVE)が選択された時、ローカルのリストビューを ソートします。

同様に「接続」(IDM_CONNECT)、「ファイルのアップロード」(IDM_UPLOAD)、 「ファイルの削除」(IDM_DELFILE)、「ディレクトリの作成」(IDM_CREATEDIR)が選択されたときには それぞれの作業後FTPのリストビューをソートします。

MyConnect, MyDown, MyGetFNameProc, SetAccount, MyAccountProc, SetHost, MyFtpAddressProcの各関数に変更はありません。

void GetAllFiles(HWND hWnd, HWND hList, HWND hStatic, INETHANDLE *lpinet) { WIN32_FIND_DATA FindFileData; HINTERNET hFindHandle; char fname[MAX_PATH + 5]; char szStr[MAX_PATH]; DWORD dwCD = MAX_PATH; LVITEM li; int iItemNo = 0; SYSTEMTIME st; if (FtpGetCurrentDirectory(lpinet->hHost, szStr, &dwCD) == TRUE) SetWindowText(hStatic, szStr); else { MessageBox(hWnd, "現在のディレクトリを取得できません", "失敗", MB_OK); return; } ListView_DeleteAllItems(hList); hFindHandle = FtpFindFirstFile(lpinet->hHost, "*.*", &FindFileData, INTERNET_FLAG_RELOAD, 0); if (hFindHandle == NULL) { if (GetLastError() == ERROR_NO_MORE_FILES) { MessageBox(hWnd, "ファイルはありません", "Error", MB_OK); return; } else { MessageBox(hWnd, "深刻なエラーが発生しました", "Error", MB_OK); InternetCloseHandle(lpinet->hHost); InternetCloseHandle(lpinet->hInternet); bConnect = FALSE; SetWindowText(hWnd, szWindowTitle); return; } } else { if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(fname, "<dir> "); strcat(fname, FindFileData.cFileName); } else { strcpy(fname, FindFileData.cFileName); } li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = fname; li.iItem = iItemNo; li.iSubItem = 0; li.lParam = iItemNo; ListView_InsertItem(hList, &li); li.mask = LVIF_TEXT; itoa(FindFileData.nFileSizeLow, li.pszText, 10); li.iSubItem = 1; ListView_SetItem(hList, &li); FileTimeToSystemTime(&FindFileData.ftLastWriteTime, &st); wsprintf(li.pszText, "%02d/%02d/%02d %02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute); li.iSubItem = 2; ListView_SetItem(hList, &li); iItemNo++; } while (InternetFindNextFile(hFindHandle, &FindFileData)) { if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(fname, "<dir> "); strcat(fname, FindFileData.cFileName); } else { strcpy(fname, FindFileData.cFileName); } li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = fname; li.iItem = iItemNo; li.iSubItem = 0; li.lParam = iItemNo; ListView_InsertItem(hList, &li); li.mask = LVIF_TEXT; itoa(FindFileData.nFileSizeLow, li.pszText, 10); li.iSubItem = 1; ListView_SetItem(hList, &li); FileTimeToSystemTime(&FindFileData.ftLastWriteTime, &st); wsprintf(li.pszText, "%02d/%02d/%02d %02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute); li.iSubItem = 2; ListView_SetItem(hList, &li); iItemNo++; } InternetCloseHandle(hFindHandle); return; }

この関数は現在のローカルのカレントディレクトリを取得して、そこにあるファイルを リストビューに表示するものですが、項目データのLVITEM構造体のmaskメンバにLVIF_PARAMを 加えるのを忘れないでください。これを忘れるとソートされません。 詳しくは第110章を参照してください。

void SetMyLocalDir(HWND hWnd, HWND hListL, HWND hStaticL) { char szfname[MAX_PATH + 5]; WIN32_FIND_DATA wfd; HANDLE hFind; int iItemNo = 0; LVITEM li; SYSTEMTIME localtm; FILETIME localft; ListView_DeleteAllItems(hListL); GetCurrentDirectory(MAX_PATH, szfname); SetWindowText(hStaticL, szfname); hFind = FindFirstFile("*.*", &wfd); if (hFind == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "エラーです", "Error", MB_OK); FindClose(hFind); return; } else { if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(szfname, "<dir> "); strcat(szfname, wfd.cFileName); } else { strcpy(szfname, wfd.cFileName); } li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = szfname; li.iItem = iItemNo; li.iSubItem = 0; li.lParam = iItemNo; ListView_InsertItem(hListL, &li); li.mask = LVIF_TEXT; itoa(wfd.nFileSizeLow, li.pszText, 10); li.iSubItem = 1; ListView_SetItem(hListL, &li); FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &localft); FileTimeToSystemTime(&localft, &localtm); wsprintf(li.pszText, "%02d/%02d/%02d %02d:%02d", localtm.wYear, localtm.wMonth, localtm.wDay, localtm.wHour, localtm.wMinute); li.iSubItem = 2; ListView_SetItem(hListL, &li); iItemNo++; } while (FindNextFile(hFind, &wfd)) { if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(szfname, "<dir> "); strcat(szfname, wfd.cFileName); } else { strcpy(szfname, wfd.cFileName); } li.mask = LVIF_TEXT | LVIF_PARAM; li.pszText = szfname; li.iItem = iItemNo; li.iSubItem = 0; li.lParam = iItemNo; ListView_InsertItem(hListL, &li); li.mask = LVIF_TEXT; itoa(wfd.nFileSizeLow, li.pszText, 10); li.iSubItem = 1; ListView_SetItem(hListL, &li); FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &localft); FileTimeToSystemTime(&localft, &localtm); wsprintf(li.pszText, "%02d/%02d/%02d %02d:%02d", localtm.wYear, localtm.wMonth, localtm.wDay, localtm.wHour, localtm.wMinute); li.iSubItem = 2; ListView_SetItem(hListL, &li); iItemNo++; } FindClose(hFind); return; }

ローカルのディレクトリ表示のときも項目データのLVITEM構造体のmaskメンバにLVIF_PARAMを 加えるのを忘れないでください。

ChangeLocalDrive, MyDriveProc, MyUp, MyFtpDelFile, MyFtpCreatDir, MyCreateDirProc, MakeMyList, InsertMyColumnの各関数に変更はありません。

int CALLBACK MyCompProc(LPARAM lp1, LPARAM lp2, LPARAM lp3) { static LVFINDINFO lvf; static int nItem1, nItem2; static char buf1[MAX_PATH], buf2[MAX_PATH]; SORTDATA *lpsd; lpsd = (SORTDATA *)lp3; lvf.flags = LVFI_PARAM; lvf.lParam = lp1; nItem1 = ListView_FindItem(lpsd->hwndList, -1, &lvf); lvf.lParam = lp2; nItem2 = ListView_FindItem(lpsd->hwndList, -1, &lvf); ListView_GetItemText(lpsd->hwndList, nItem1, lpsd->isortSubItem, buf1, sizeof(buf1)); ListView_GetItemText(lpsd->hwndList, nItem2, lpsd->isortSubItem, buf2, sizeof(buf2)); if (lpsd->iUPDOWN == UP) { return(stricmp(buf1, buf2)); } else { return(stricmp(buf1, buf2) * -1); } }

アプリケーション定義比較関数です。lp3から3つのデータ (リストビューのハンドル、ソートする基準となるサブアイテムのインデックス、 昇順か降順か)を受け取っています。

さて、この定義関数では「サイズ」でソートしたとき、ファイルサイズが 辞書式にソートされてしまいます。「サイズ」でソートするとき数字で ソートするように改良してみてください。

今回もそれほど難しくはありませんでした。しかし、ちょっとした間違いで 非常にわかりにくいバグが発生するので注意してください。


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

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