|
ファイルのコピーや削除には、いろいろな方法があり、ここで示したのは、あくまで一例です。
サンプルは、極力シンプルに考えました。
そのため、サンプルを走らせると、多少違和感があります。
フォルダの中身の読み込みと、リストビューの描画は、関数にしています。
1.アプリケーション名とグローバル変数
サンプルのアプリケーション名は Directory、
シングルドキュメント/フォームビューです。
開発環境を置いたディレクトリにデータ用のフォルダ、「folder1」、「folder2」 を新規作成してください。
注:開発環境でデータ用のフォルダを置く階層は、res フォルダと同列です。
別な場所に置く場合は、パスを書き換えてください。
先に変数を宣言しておきます。
DirectoryView.cpp
|
#include "DirectoryDoc.h"
#include "DirectoryView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//---------------------------------------------■ここから
int ret;
CString name1[10], name2[10]; //ディレクトリーから読み取ったファイル・フォルダ名
TCHAR fullpath[500]; //フルパス
//---------------------------------------------■ここまで
|
|
2.コントロールとメンバ変数
ボタンはそれぞれ、OnBnClickedButton 関数を作っておいてください。
3.リストビュー描画の関数化(DirectoryView.cpp−list1draw(・・)/list2draw(・・))
リストビューへの描画は、関数にしてしまいました。
クラスビューの CDirectoryView から 関数の追加 で、void list1draw( int ti ) 関数と void list2draw( int ti ) 関数を作成してください。
(1) DirectoryView.cpp−list1draw(int ti)
|
|
void CDirectoryView::list1draw(int ti)
{ |
|
|
//---------------------------------------------■ここから
//■listview1への出力
TCHAR mytch1[100];
m_list1.DeleteAllItems(); m_list1.DeleteColumn(0);
LVCOLUMN myColumn; myColumn.mask = LVCF_TEXT|LVCF_WIDTH;
myColumn.cx = 195; myColumn.pszText = _T("folder1");
m_list1.InsertColumn(0,&myColumn);
myColumn.mask = LVCF_TEXT|LVCF_WIDTH;
static LVITEM myITEM;
for ( int i=0; i<=ti; i++ ) { |
|
|
|
myITEM.mask =LVIF_TEXT; myITEM.iItem = i; myITEM.iSubItem = 0;
_tcscpy_s( mytch1, name1[i] );
myITEM.pszText = mytch1; m_list1.InsertItem(&myITEM); |
|
|
}
m_list1.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_TWOCLICKACTIVATE|LVS_EX_SUBITEMIMAGES);
long state; state = ::GetWindowLong(m_list1.GetSafeHwnd(),GWL_STYLE);
::SetWindowLong(m_list1.GetSafeHwnd(),GWL_STYLE,(state & ~LVS_TYPEMASK)|LVS_REPORT);
//---------------------------------------------■ここまで |
|
}
|
|
(2) DirectoryView.cpp−list2draw(int ti)
|
|
void CDirectoryView::list2draw(int ti)
{ |
|
|
//---------------------------------------------■ここから
//■listview2への出力
TCHAR mytch1[100];
m_list2.DeleteAllItems(); m_list2.DeleteColumn(0);
LVCOLUMN myColumn; myColumn.mask = LVCF_TEXT|LVCF_WIDTH;
myColumn.cx = 195; myColumn.pszText = _T("folder2");
m_list2.InsertColumn(0,&myColumn);
myColumn.mask = LVCF_TEXT|LVCF_WIDTH;
static LVITEM myITEM;
for (int i=0; i<=ti; i++) { |
|
|
|
myITEM.mask =LVIF_TEXT; myITEM.iItem = i; myITEM.iSubItem = 0;
_tcscpy_s( mytch1, name2[i] );
myITEM.pszText = mytch1; m_list2.InsertItem(&myITEM); |
|
|
}
m_list2.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_TWOCLICKACTIVATE|LVS_EX_SUBITEMIMAGES);
long state; state = ::GetWindowLong(m_list2.GetSafeHwnd(),GWL_STYLE);
::SetWindowLong(m_list2.GetSafeHwnd(),GWL_STYLE,(state & ~LVS_TYPEMASK)|LVS_REPORT);
//---------------------------------------------■ここまで |
|
}
|
|
4.フルパスの取得(DirectoryView.cpp−OnInitialUpdate)
フルパスは後々使いますので、サンプルでは、TCHAR型の変数 fullpath をグローバル変数で定義して、値を OnInitialUpdate の中で取得するようにしました。
露骨に書き込んだ 500 は、最大文字長です。
OnInitialUpdate()で、唐突に read_directory1() と read_directory2() が登場しますが、これはフォルダやファイルの読み込みを関数化したものです。
後で作成します。
|
void CDirectoryView::OnInitialUpdate()
{
|
|
|
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
//---------------------------------------------■ここから //■フルパスの取得
GetCurrentDirectory(500, fullpath); //■現在のフォルダ内の状態を確認
list1draw(read_directory1()); list2draw(read_directory2()); //---------------------------------------------■ここまで |
|
);
|
|
5.作成(DirectoryView.cpp−OnBnClickedButton1())
@ フォルダの作成
CreateDirectory() 一発です。相対パスでもOKです。サンプルでは 「folder1」 の中に newfolder というフォルダを作っています。
A ファイルの作成
いまどき空ファイルを作成することなどあるかなぁと思いましたが、サンプルを作るのには必要でした。
それで、こそこそっとMSDNへ行って、関数を拾ってきました。
テストしてみたところ、すでに同じ名前のファイルがあると、上書きされて、元のファイルの中身は消えます。
サンプルでは、newfile.txt というファイルを作っています。
|
void CDirectoryView::OnBnClickedButton1()
{ |
|
|
//-----------------------------------------------------■ここから
TCHAR mytch1[500], mytch2[500];
//■フォルダの作成
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1, _T("\\folder1\\newfolder"));
CreateDirectory(mytch1,NULL);
//■ファイルの作成
_tcscpy_s(mytch2,fullpath);
_tcscat_s(mytch2, _T("\\folder1\\newfile.txt"));
CreateFile( |
|
|
|
mytch2, // ファイル名
0, // アクセスモード
FILE_SHARE_DELETE, // 共有モード
NULL , // セキュリティ記述子
CREATE_ALWAYS, // 作成方法
FILE_ATTRIBUTE_ARCHIVE, // ファイル属性
NULL // テンプレートファイルのハンドル |
|
|
);
//■ディレクトリー内のフォルダ/ファイル名の読み込みとリストビューへの出力
list1draw(read_directory1());
//---------------------------------------------■ここまで |
|
}
|
|
6.特定のディレクトリー内にあるファイル、フォルダ名の読み込み(DirectoryView.cpp−read_directory1())
folder1、folder2 にあるフォルダとファイルの名前を、各々 CString型 の配列 name1[]、name2[] に読み込むコードで、関数にしています。
クラスビューの CDirectoryView から 関数の追加 で、、 read_directory1 関数と read_directory2 関数を作成してください。(戻り値 int 型、パラメータなし)
追加コードをコピペします。
return 0 は、コメントアウトしてください。
@ folder1の親ディレクトリ (exeファイルのあるディレクトリ) と、そのまた親のディレクトリの名前は、'.' と '..' で取得されますので、if(filefind.IsDots()) {continue; } で
蹴り飛ばしています。
(理解しにくければ、試しに if(filefind.IsDots()) {continue; } をコメントアウトして、実行してみてください)
A ファイルだけ読み取る場合は、_tcscat_s(mytch1,"\\folder1\\*") の \\* を \\*.* と置き換えてください。
B 同じ要領で read_directory2() 関数も作成してください。
read_directort1で取得したファイル名は、name1[]配列に、read_directort2で取得したファイル名は、name2[]配列に、収納します。
|
int CDirectoryView::read_directory1(void)
{ |
|
|
//---------------------------------------------■ここから
//■ディレクトリー1のフォルダ、ファイル名の読込み
BOOL b; CFileFind filefind; TCHAR mytch1[500]; int f = -1;
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1,_T("\\folder1\\*"));//←B
if( filefind.FindFile(mytch1) ) { |
|
|
|
do { |
|
|
|
|
b = filefind.FindNextFile();
if(filefind.IsDots()) {continue; }//←A
name1[++f] = filefind.GetFileName();
|
|
|
|
} while( b );
filefind.Close(); |
|
|
}
return f;
//---------------------------------------------■ここまで |
|
}
|
|
|
int CDirectoryView::read_directory2(void)
{ |
|
|
//---------------------------------------------■ここから
//■ディレクトリー2のフォルダ、ファイル名の読込み
BOOL b; CFileFind filefind; TCHAR mytch1[500]; int f = -1;
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1,_T("\\folder2\\*"));//←B
if( filefind.FindFile(mytch1) ) { |
|
|
|
do { |
|
|
|
|
b = filefind.FindNextFile();
if(filefind.IsDots()) {continue; }//←A
name2[++f] = filefind.GetFileName();
|
|
|
|
} while( b );
filefind.Close(); |
|
|
}
return f;
//---------------------------------------------■ここまで |
|
}
|
|
7.ファイル名、フォルダ名のリネーム(DirectoryView.cpp−OnBnClickedButton2())
@ SHFileOperation()
先に SHFileOperation について触れます。
このページでは、フォルダについては、リネーム、コピー、移動、削除とも、SHFileOperation を使っています。
SHFileOperation には、フォルダの中に入っているファイルまで一括で処理してくれるメリットがあります。
ただし相対パスは使えません。
また pFrom には、末尾に _T(”\0\0”) をつける必要があります。
_tcscpy_s() や _tcscat_s() に _T(”\0\0”) は使えませんので、サンプルでは、mytch1[_tcsclen(mytch1)+1] = 0; としています。
A フォルダ名のリネーム
前述の SHFileOperation で、fo.wFunc を FO_RENAME にすると、「フォルダ名の変更」になります。
FO_MOVE のほうがいいかもしれません。
B ファイル名のリネーム
MoveFile が簡単でわかり易いと思います。
同じディレクトリに、名前を変えて移動するというイメージです。
MoveFile は移動先にすでに同じ名前のファイルがあると、上書きされず 0 を返してきます。
サンプルでは、返り値が 0 の時は、メッセージボックスがポップアップするようにしました。
もし同名のファイルがすでに存在しても、問答無用で上書きするなら、SHFileOperation です。
|
void CDirectoryView::OnBnClickedButton2()
{ |
|
|
//---------------------------------------------■ここから
//■ファイル名/フォルダ名の変更
TCHAR mytch1[500], mytch2[500], mytch3[500], mytch4[500];
//■フォルダ名の変更
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1,_T("\\folder1\\newfolder"));
mytch1[_tcsclen(mytch1)+1] = 0;
_tcscpy_s(mytch2,fullpath);
_tcscat_s(mytch2,_T("\\folder1\\renamefolder"));
SHFILEOPSTRUCT fo;
fo.hwnd = m_hWnd;
fo.wFunc = FO_RENAME;
fo.pFrom = mytch1;
fo.pTo = mytch2;
fo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
ret=SHFileOperation(&fo);
//■ファイル名の変更
_tcscpy_s( mytch3, fullpath);
_tcscat_s(mytch3,_T("\\folder1\\newfile.txt"));
_tcscpy_s( mytch4, fullpath);
_tcscat_s(mytch4,_T("\\folder1\\renamefile.txt"));
//■MoveFileに失敗した時の処理
if(int fc = MoveFile(mytch3, mytch4) == 0) { |
|
|
|
ret=AfxMessageBox(_T("newfile.txtをrenamefile.txtに変更できませんでした"),
MB_OK | MB_ICONEXCLAMATION); |
|
|
}
//■ディレクトリー内のフォルダ/ファイル名の読み込みとリストビューへの出力
list1draw(read_directory1());
//---------------------------------------------■ここまで |
|
}
|
|
|
8.コピー (DirectoryView.cpp−OnBnClickedButton3())
@ フォルダのコピー
ここでもSHFileOperation を使い、fo.wFunc を FO_COPY にしています。
サンプルで作成したフォルダに、なにかファイルを落とし込んで、ファイルごとコピーされていることを確認してください。
A ファイルのコピー
CopyFile を使ってみました。
true は、すでに同名のファイルがあった場合に上書きなしで返り値 0( コピー失敗 )、false は上書きでコピー成功です。
注:サンプルはファイル/フォルダ名をハードコーディングしていますので、コピーできるのは、newfolder と newfile.txt だけです。
|
void CDirectoryView::OnBnClickedButton3()
{ |
|
|
//---------------------------------------------■ここから
//■コピー
TCHAR mytch1[500], mytch2[500], mytch3[500], mytch4[500];
//■フォルダのコピー
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1,_T("\\folder1\\newfolder"));
mytch1[_tcsclen(mytch1)+1] = 0;
_tcscpy_s(mytch2,fullpath);
_tcscat_s(mytch2,_T("\\folder2\\newfolder"));
SHFILEOPSTRUCT fo;
fo.hwnd = m_hWnd;
fo.wFunc = FO_COPY;
fo.pFrom = mytch1;
fo.pTo = mytch2;
fo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
ret=SHFileOperation(&fo);
//■ファイルのコピー
_tcscpy_s( mytch3, fullpath);
_tcscat_s(mytch3,_T("\\folder1\\newfile.txt"));
_tcscpy_s( mytch4, fullpath);
_tcscat_s(mytch4,_T("\\folder2\\newfile.txt"));
CopyFile(mytch3, mytch4, true);
//■ディレクトリー内のフォルダ/ファイル名の読み込みとリストビューへの出力
list1draw(read_directory1());
list2draw(read_directory2());
//---------------------------------------------■ここまで |
|
}
|
|
9.移動 (DirectoryView.cpp−OnBnClickedButton4())
@ フォルダの移動
SHFileOperation & FO_MOVE 一発です。
A ファイルの移動
MoveFile を使いました。
サンプルではリネームの時と同じように、移動先にすでに同名のファイルがあって失敗した時、メッセージボックスがポップアップするようにしてあります。
注:サンプルはファイル/フォルダ名をハードコーディングしていますので、コピーできるのは、newfolder と newfile.txt だけです。
|
void CDirectoryView::OnBnClickedButton4()
{ |
|
|
//---------------------------------------------■ここから
//■移動
TCHAR mytch1[500], mytch2[500], mytch3[500], mytch4[500];
//■フォルダの移動
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1,_T("\\folder1\\newfolder"));
mytch1[_tcsclen(mytch1)+1] = 0;
_tcscpy_s(mytch2,fullpath);
_tcscat_s(mytch2,_T("\\folder2\\newfolder"));
SHFILEOPSTRUCT fo;
fo.hwnd = m_hWnd;
fo.wFunc = FO_MOVE;
fo.pFrom = mytch1;
fo.pTo = mytch2;
fo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
ret=SHFileOperation(&fo);
//■ファイルの移動
_tcscpy_s( mytch3, fullpath);
_tcscat_s(mytch3,_T("\\folder1\\newfile.txt"));
_tcscpy_s(mytch4, fullpath);
_tcscat_s(mytch4,_T("\\folder2\\newfile.txt"));
int fc = MoveFile(mytch3, mytch4);
//■MoveFileに失敗した時の処理
if(fc == 0) { |
|
|
|
ret=AfxMessageBox(_T("newfile.txtを移動できませんでした"), MB_OK | MB_ICONEXCLAMATION); |
|
|
}
//■ディレクトリー内のフォルダ/ファイル名の読み込みとリストビューへの出力
list1draw(read_directory1());
list2draw(read_directory2());
//---------------------------------------------■ここまで |
|
}
|
|
10.削除 (DirectoryView.cpp−OnBnClickedButton5())
@ ダウンロードサンプルとのコードの違い
ダウンロードサンプルでは、フォルダ、ファイルとも、存在する可能性のある3個づつについて削除をかけています。
同じことの繰り返しなので、ここに掲載しているのは、1個分だけです。
もし指定したファイルが存在していなくても、戻り値は気にしないでおこう、という感じです。
実際には、もう少しキメの細かい配慮が必要だと思います。
A フォルダの削除
SHFileOperation & FO_DELETE でいきました。
フォルダの中にフォルダやファイルがあっても、これだけですっきりごみ箱へ移動してくれます。
もちろん、ごみ箱を使わず、一発で「帰らぬ人」にすることもできます。
B ファイルの削除
普段なにげに使っている remove で、と思ったのですが、エラーになるケースが多いようなので、ここも SHFileOperation() & FO_DELETE にしました。
もちろんフォルダと同じように、ごみ箱へ移動することができます。
|
void CDirectoryView::OnBnClickedButton5()
{ |
|
|
//---------------------------------------------■ここから
//■削除
TCHAR mytch1[500], mytch2[500];
//■フォルダの削除
_tcscpy_s(mytch1,fullpath);
_tcscat_s(mytch1,_T("\\folder1\\newfolder"));
mytch1[_tcsclen(mytch1)+1] = 0;
SHFILEOPSTRUCT fo;
fo.hwnd = m_hWnd;
fo.wFunc = FO_DELETE;
fo.pFrom = mytch1;
fo.pTo = 0;
fo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
ret=SHFileOperation(&fo);
//■ファイルの削除
_tcscpy_s(mytch2,fullpath);
_tcscat_s(mytch2,_T("\\folder1\\newfile.txt"));
mytch2[_tcsclen(mytch2)+1] = 0;
fo.hwnd = m_hWnd;
fo.wFunc = FO_DELETE;
fo.pFrom = mytch2;
fo.pTo = 0;
fo.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
ret=SHFileOperation(&fo);
//■その他のフォルダ、ファイルの削除は、単なる繰り返しなので省略。必要な場合は、サンプルをダウンロードしてください
//■ディレクトリー内のフォルダ/ファイル名の読み込みとリストビューへの出力
list1draw(read_directory1());
list2draw(read_directory2());
//---------------------------------------------■ここまで
|
|
}
|
|
このサンプルを書くにあたって、なるべく多様な関数や構造体を使ってみようと思ったのですが、結局 SHFileOperation() の使い勝手のよさばかりが目立つ結果となりました。
中身ぎっしりのフォルダを一発で操作できるのがうれしいです。
このサンプルは、当初、簡単に仕上げるつもりでいたのですが、いろいろ調べることが多く、私自身、たいへん勉強になりました。
|