左側にローカルのディレクトリ、右側にFTPサーバのディレクトリが
表示されます。左の図ではFTPのpublic_html/index.htmlがローカルの
デスクトップにダウンロードするよう選択されています。
このあと、メニューから「ファイルのダウンロード」を選択するだけです。
さて、上のプログラムではローカルのディレクトリを移動するには
ディレクトリ名をダブルクリックします。しかし、ドライブを
変更することはできません。変更するにはメニューの「ローカル」
「ドライブの変更」を選択します。
すると、左の図のようなダイアログボックスが出てきますので これで、ドライブを変更します。
では、プログラムを見てみましょう。
メニューに「ローカル」「ドライブの変更」が追加されました。// myftp03.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "ftp(&T)" BEGIN MENUITEM "接続(&C)", IDM_CONNECT MENUITEM "ファイルのダウンロード(&D)", IDM_DOWNLOAD MENUITEM "切断(&X)", IDM_DISCONNECT END POPUP "ローカル(&L)" BEGIN MENUITEM "ドライブの変更(&D)", IDM_LOCALDRIVE END POPUP "設定(&S)" BEGIN MENUITEM "アカウント設定(&A)", IDM_ACCOUNT MENUITEM "ホスト設定(&H)", IDM_GETHOST END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYACCOUNT DIALOG DISCARDABLE 0, 0, 143, 67 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "アカウント" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,46,50,14 PUSHBUTTON "キャンセル",IDCANCEL,86,46,50,14 LTEXT "ID",IDC_STATIC,7,7,8,8 LTEXT "パスワード",IDC_STATIC,7,25,32,8 EDITTEXT IDC_ID,43,7,94,13,ES_AUTOHSCROLL EDITTEXT IDC_PASSWORD,43,25,94,13,ES_PASSWORD | ES_AUTOHSCROLL END MYFTPADDRESS DIALOG DISCARDABLE 0, 0, 159, 63 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "ホスト" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,42,50,14 PUSHBUTTON "キャンセル",IDCANCEL,102,42,50,14 LTEXT "FTPアドレス",IDC_STATIC,7,7,36,8 LTEXT "基準となるディレクトリ",IDC_STATIC,7,25,64,8 EDITTEXT IDC_FTPADDRESS,75,7,78,13,ES_AUTOHSCROLL EDITTEXT IDC_BASEDIR,75,25,78,13,ES_AUTOHSCROLL END MYGETFNAME DIALOG DISCARDABLE 0, 0, 187, 67 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "ファイル名入力" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,46,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,46,50,14 LTEXT "ダウンロードするファイル",IDC_STATIC,7,7,72,8 EDITTEXT IDC_FNAME,88,7,92,12,ES_AUTOHSCROLL LTEXT "ダウンロード先ファイル名",IDC_STATIC,7,31,74,8 EDITTEXT IDC_LOCALFILE,88,27,92,12,ES_AUTOHSCROLL END MYDRIVE DIALOG DISCARDABLE 0, 0, 43, 115 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "ドライブ" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,9,94,24,14 LISTBOX IDC_LIST1,7,7,29,83,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP END
今回新たに、ローカルのディレクトリを表示する子ウィンドウを 2つ作ったのでそれぞれのIDを新たに定義しました(ID_LIDTL, ID_STATICL)。// myftp03.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <wininet.h> #include <windowsx.h> #include "resource.h" #define ID_LIST 100 #define ID_STATIC 101 #define ID_LISTL 102 #define ID_STATICL 103 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; 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); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL MyConnect(HWND, INETHANDLE *, ACCOUNT, FTPADDRESS, HWND, HWND); void MyDown(HWND, INETHANDLE, FTPADDRESS, FNAME *, 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); const char szWindowTitle[] = "猫でもわかるFTP"; char szClassName[] = "myftp03"; //ウィンドウクラス BOOL bConnect = FALSE;//インターネットに接続しているかどうか
また、ダイアログボックスに2つのHWNDを渡したい関係からHWNDSETという 構造体を定義してみました(後出)。
ドライブ変更のためのダイアログボックスのプロシージャ(MyDriveProc) を新しく作ります。
ファイルをダウンロードするための関数MyDownの引数が増えています。 リストボックスにローカルのディレクトリを表示するSetMyLocalDir関数、 ローカルのドライブを変更するChangeLocalDrive関数が追加になりました。
WinMain, InitApp, InitInstanceの各関数に変更はありません。
前章では自作構造体FNAME(ダウンロードする ファイル名とローカルのファイル名を格納する)はWndProcプロシージャ内には 登場してきませんでした。しかし、後のプログラムの関係上ここで 登場させます。あちこちで使うのでここの関数内にstaticな変数として 宣言しておくと便利です。//ウィンドウプロシージャ 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]; switch (msg) { case WM_CREATE: hInst = ((LPCREATESTRUCT)lp)->hInstance; GetClientRect(hWnd, &rc); hList = CreateWindowEx(0, "LISTBOX", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_SORT | LBS_NOTIFY, 0, 0, 0, 0, hWnd, (HMENU)ID_LIST, hInst,NULL); hListL = CreateWindowEx(0, "LISTBOX", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_SORT | LBS_NOTIFY, 0, 0, 0, 0, hWnd, (HMENU)ID_LISTL, hInst, NULL); 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); 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_COMMAND: switch (LOWORD(wp)) { case ID_LIST: if (HIWORD(wp) == LBN_DBLCLK) { iIndex = ListBox_GetCurSel(hList); ListBox_GetText(hList, iIndex, szDir); for (i = 0; i <= lstrlen(szDir) -5 ; i++) { szBuf[i] = szDir[i + 6]; } FtpSetCurrentDirectory(inet.hHost, szBuf); GetAllFiles(hWnd, hList, hStatic, &inet); } break; case ID_LISTL: if (HIWORD(wp) == LBN_DBLCLK) { iIndex = ListBox_GetCurSel(hListL); ListBox_GetText(hListL, iIndex, szDir); for (i = 0; i <= lstrlen(szDir) - 5; i++) { szBuf[i] = szDir[i + 6]; } SetCurrentDirectory(szBuf); SetMyLocalDir(hWnd, hListL, hStaticL); } break; case IDM_LOCALDRIVE: ChangeLocalDrive(hWnd, hListL, hStaticL); break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_CONNECT: bConnect = MyConnect(hWnd, &inet, myaccount, myftpaddress, hList, hStatic); break; case IDM_DOWNLOAD: MyDown(hWnd, inet, myftpaddress, &myfname, hList, hStaticL); 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; }
WM_CREATEのところで今回新たに増えた子供ウィンドウ(ローカルのディレクトリ表示用) を作ります。子供を作ったらSetMyLocalDir関数(後出)を呼んでローカルのディレクトリを 表示させます。
WM_SIZEメッセージが来たら4つの子供ウィンドウの位置、大きさを 調整します。ローカルとFTPの表示用ウィンドウは同じ大きさになるように してあります。
ローカルディレクトリ表示用のリストボックスがダブルクリックされたら そこの文字列を取得してここから「<dir> 」を取り除きます。 そして、SetCurrentDirectory関数でカレントディレクトリを変更します。 そして、SetMyLocalDir関数を呼んで新しいディレクトリ表示をさせます。 ftpのディレクトリ変更とほとんど同じです。
カレントディレクトリを変更します。BOOL SetCurrentDirectory( LPCTSTR lpPathName );
メニューからIDM_LOCALDRIVE(「ドライブの変更」)が選択されたら ChangeLocalDrive関数(後出)を呼び出してドライブの変更を行います。
メニューからIDM_DOWNLOAD(「ファイルのダウンロード」)が選択されたら MyDown関数を呼びますが、引数が変更になっているので注意してください。
プログラム終了時、今回新しく作った2つの子供ウィンドウを破棄します。 この操作は省略しても差し支えありません。(親が死んだら子供も自動的に死ぬ)
MyConnect関数に変更はありません。
今までは、ファイルのダウンロードは自分で「ダウンロードするファイル名」と 「ダウンロード先」を入力しなくてはいけませんでした。void MyDown(HWND hWnd, INETHANDLE inet, FTPADDRESS ftpadr, FNAME *lpfname, HWND hList, HWND hStaticL) { HINSTANCE hInst; int iIndex; if (!bConnect) { MessageBox(hWnd, "インターネットに接続されていません", "失敗", MB_OK); return; } iIndex = ListBox_GetCurSel(hList); ListBox_GetText(hList, iIndex, lpfname->szFName); GetWindowText(hStaticL, lpfname->szLocalFileName, sizeof(lpfname->szLocalFileName)); strcat(lpfname->szLocalFileName, "\\"); strcat(lpfname->szLocalFileName, lpfname->szFName); hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); if (DialogBoxParam(hInst, "MYGETFNAME", hWnd, (DLGPROC)MyGetFNameProc, (LPARAM)lpfname) == IDCANCEL) return; if (!FtpGetFile(inet.hHost, //FTPのインターネットハンドル lpfname->szFName, //ダウンロードするファイル lpfname->szLocalFileName, //ダウンロード先のパス付ファイル名 TRUE, //ダウンロード先に同名のファイルがあるときエラーにする(上書き防止) FILE_ATTRIBUTE_NORMAL, //ダウンロード先に作られるファイルのアトリビュート FTP_TRANSFER_TYPE_BINARY, //バイナリファイルとしてダウンロード 0 )) { MessageBox(hWnd, "ダウンロードに失敗しました。", "失敗", MB_OK); return; } MessageBox(hWnd, "無事ダウンロードできました", "OK", MB_OK); return; }
リストボックスで指定したファイルとか、パスを自動的に入力してくれると 大変助かります。この関数が呼ばれたらダウンロードするファイルは hListで選択されているファイル、ダウンロード先のパスはhStaticLの 文字列(ウィンドウタイトル)にhListのファイル名をくっつけたものです。
さて、これでこの関数の引数を変更した理由がわかったでしょうか。 FNAME構造体のアドレスを引数でもらってきたので、この中身をを変更すると 呼び出し元のWndProc内のFNAME構造体の内容も変更されます。 WndProc内ではstaticで宣言されているので、変更された内容は ずーっと保持されます。それで、いろいろなところで利用できるようになります。
MyGetFNameProc, SetAccount, MyAccountProc, SetHost, MyFtpAddressProc, GetAllFilesの各関数に変更はありません。
リストボックスにローカルのディレクトリを表示する関数です。 FTPのディレクトリを表示するのとほとんど同じ方法です。 まず現在のディレクトリ名を取得してhStaticLに表示します。void SetMyLocalDir(HWND hWnd, HWND hListL, HWND hStaticL) { char szfname[MAX_PATH + 5]; WIN32_FIND_DATA wfd; HANDLE hFind; ListBox_ResetContent(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); } ListBox_AddString(hListL, szfname); } while (FindNextFile(hFind, &wfd)) { if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { strcpy(szfname, "<dir> "); strcat(szfname, wfd.cFileName); } else { strcpy(szfname, wfd.cFileName); } ListBox_AddString(hListL, szfname); } FindClose(hFind); return; }
nBufferLengthには、バッファサイズを指定します。DWORD GetCurrentDirectory( DWORD nBufferLength, LPTSTR lpBuffer );
lpBufferカレントディレクトリの絶対パスが格納されるバッファです。
成功するとバッファに書き込まれた文字数、失敗すると0が返されます。
lpFileNameには、パス名とファイル名が入った文字列へのポインタを指定します。HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData );
lpFindFileDataにはWIN32_FIND_DATA構造体へのポインタを指定します。 この構造体については前章を参照してください。
関数が成功すると検索ハンドルが返され、失敗するとINVALID_HANDLE_VALUEが 返されます。
以前のFindFirstFile関数に続けて、次のファイルを探します。BOOL FindNextFile( HANDLE hFindFile, LPWIN32_FIND_DATA lpFindFileData );
hFindFileにはFindFirstFile関数で返された検索ハンドルを指定します。
lpFindFileDataはWIN32_FIND_DATA構造体へのポインタです。
検索ハンドルをクローズします。BOOL FindClose( HANDLE hFindFile );
ローカルのディレクトリを変更する関数です。 WndProcからもらってきたローカルディレクトリを表示する スタティックウィンドウとリストボックスのハンドルを プロシージャに渡すためにHWNDSET構造体のメンバにセットします。 そして、このアドレスをDialogBoxPalam関数の最後の引数に渡します。void ChangeLocalDrive(HWND hWnd, HWND hListL, HWND hStaticL) { HINSTANCE hInst; HWNDSET hwndset; hwndset.hwnd1 = hListL; hwndset.hwnd2 = hStaticL; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); DialogBoxParam(hInst, "MYDRIVE", hWnd, (DLGPROC)MyDriveProc, (LPARAM)&hwndset); return; }
ドライブ変更ダイアログボックスのプロシージャです。LRESULT CALLBACK MyDriveProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hList; static HWND hListL, hStaticL, hParent; char szDrv[4]; HWNDSET *lphset; switch (msg) { case WM_INITDIALOG: lphset = (HWNDSET *)lp; hListL = lphset->hwnd1; hStaticL = lphset->hwnd2; hParent = GetParent(hDlg); hList = GetDlgItem(hDlg, IDC_LIST1); DlgDirList(hDlg, "*.*", IDC_LIST1, 0, DDL_DRIVES); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: DlgDirSelectEx(hDlg, szDrv, sizeof(szDrv), IDC_LIST1); strcat(szDrv, "\\"); SetCurrentDirectory(szDrv); SetMyLocalDir(hParent, hListL, hStaticL); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }
WM_INITDIALOGがきたらChangeLocalDrive関数からもらってきた
HWNDSET構造体を調べてstaticな変数(hListL, hStaticL)に格納します。
また親ウィンドウ(メインウィンドウ)のハンドルも取得しておきます。
ここで、出てくるhListはこのダイアログのドライブ名を表示する
リストボックスのハンドルです。
さて、次にDlgDirList関数でドライブを調べます。
リストボックスにファイル名、ディレクトリ名、ドライブ名を表示します。int DlgDirList( HWND hDlg, LPTSTR lpPathSpec, int nIDListBox, int nIDStaticPath, UINT uFileType );
hDlgには、リストボックスを持つダイアログボックスのハンドルを指定します。
lpPathSpecには、パス名等を指定します。
nIDListBoxには、リストボックスのIDを指定します。
nIDStaticPathにはスタティックコントロールのIDを指定します。
uFileTypeには表示するファイルのタイプを指定します。
次の値の組み合わせで指定します。
DDL_ARCHIVE | アーカイブファイルを表示します。 |
DDL_DIRECTORY | サブディレクトリを表示します。 |
DDL_DRIVES | ドライブを表示します。 |
DDL_HIDDEN | 隠しファイルを表示します。 |
DDL_EXCLUSIVE | 指定された属性を持つファイルのみを表示します。 |
DDL_READONLY | 読取専用ファイルを表示します。 |
DDL_READWRITE | 読み書き可能ファイルを表示します。 |
DDL_SYSTEM | システムファイルを表示します。 |
DDL_POSTMSGS | メッセージをアプリケーションメッセージキューにポストします。 通常は直接ダイアログボックスプロシージャに送られます。 |
この関数を用いると普通のリストボックスに簡単にファイル名等を表示できます。 ローカルのディレクトリ表示にこれを使えばよいではないか、と思われるかもしれませんが この関数は古いタイプのファイル名しかサポートしていません。また、 サブディレクトリ名に日本語が使われていると文字化けします。それで 今回はドライブ名の取得のみに使っています。
OKボタンが押されたらDlgDirSelectEx関数で選択されているドライブ名を取得します。
hDlgには、リストボックスを持つダイアログボックスのハンドルを指定します。BOOL DlgDirSelectEx( HWND hDlg, LPTSTR lpString, int nCount, int nIDListBox );
lpStringには、選択項目が格納されるバッファへのポインタを指定します。
nCountには、バッファサイズを指定します。
nIDListBoxには、リストボックスのIDを指定します。
これで、簡単に選択された項目が取得できます。(ドライブとかサブディレクトリに ついてくるかっこなどを取り外してくれる)
さて、選択されたドライブがわかったらSetCurrentDirectory関数で カレントディレクトリを変更し、SetMyLocalDir関数で ディレクトリ表示を行います。
今回の改良で少し使いやすくなりましたが、まだまだ不満はあります。 少しずつ改善していきましょう。
Update 02/Aug/1999 By Y.Kumei