Visual C++ 2008 Express Editionでアプリを作る(その13)

取りあえず動いたので、おしまい。
やらなきゃいけないことが沢山残っている。スグに思いつくだけでも

  • SHFileOperationを1ファイル/フォルダ毎に呼び出しているが、パスを繋げて呼ぶようにするべき。
  • ファイルの移動はMoveFileExを使った方がこのツールの目的にあう。
  • 環境設定フォームで、設定パスの変更は「ユーザ設定」のradioButtonが選択されている時にするべき。
  • %Y %M %Dなどで実行時の日付でフォルダを生成できるようにするべき。

など、色々ある。感じとしてはプロトタイプを作ったレベルでしかない。
当初、プロジェクトを丸ごと公開しようと思っていたけど、VC++が自動生成したコードなどを公開しても良いのかわからなかったので、自分が書いた部分のコードを簡単な説明を添えて貼ることにする。コードを貼ってみると長かったです。検索で来てしまった人ごめんなさい。

Form1.h

起動して表示されるメイン画面。
今にして思えば、設定情報を保持する_confはスグにgcnewしてしまえば良かった。アプリの動作中 = Form1のインスタンスが存在しているのだし。
後やっぱり、VC++のクラスエディタがヘッダファイルにソースコードを書かせるのは、初心者の教育に悪いので止めて欲しい。ファイルが散らかると、エディタの実装が複雑になるのはわかるけど。

public ref class Form1 : public System::Windows::Forms::Form
{
(略)
private:
   gdcConf^ _conf; //設定情報
private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
      FileTool^ ftool = gcnew FileTool();
      switch (_conf->Radiobutton_number) {
         case TRASH_BOX:
            ftool->DeleteFoldersAndFiles();
            break;
         case USER_CONF:
            ftool->MoveFoldersAndFiles(_conf->UsrSavePath);
            break;
         default:
            break;
      }
   }
private: System::Void MainMenuItem_SavePlace_Click(System::Object^  sender, System::EventArgs^  e) {
      Form_SavePlace^ fsp = gcnew Form_SavePlace(_conf);
      fsp->ShowDialog();
   }
private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
      this->Close();
   }
private: System::Void Form1_Load(System::Object^  sender, System::EventArgs^  e) {
      _conf = gcnew gdcConf();
   }
};

gdcConf.h

設定情報を保持するために作ったクラスです。
定数を作るためにconst intを使っています。C++/CLIの作法的に正しいのかわかりません。
ヘッダファイルに、コーディング…。

const int TRASH_BOX = 1; // ゴミ箱
const int USER_CONF = 2; // ユーザー設定

public ref class gdcConf
{
private:
   int _radiobutton_number; // radioボタンの設定値 1:ゴミ箱 2:ユーザ設定 
   String^ _UsrSavePath;    // ユーザー設定の保存場所のパス
   literal String^ CONF_FILE_PATH = "./gdcConf.ini"; // 設定ファイル名
   literal String^ DEFAULT_CONF = "1\n\n";           // デフォルト設定

public:
   gdcConf(void);
   gdcConf(String^ newSavePath);
   void WriteFile(void); // 設定ファイルへの保存 (Readはインスタンス生成時に実行してしまう)
   property String^ UsrSavePath {
      String^ get()
      {
         return _UsrSavePath;
      }
      void set(String^ newPath)
      {
         if (newPath->EndsWith((Path::DirectorySeparatorChar).ToString()) != true) {
         // 最後が"\"でない
            newPath = String::Concat(newPath, (Path::DirectorySeparatorChar).ToString());
         }
         _UsrSavePath = newPath;
      }
   }
   property int Radiobutton_number {
      int get()
      {
         return _radiobutton_number;
      }
      void set(int newNumber)
      {
         if (newNumber > 0) {
            _radiobutton_number = newNumber;
         }
      }
   }
};

gdcConf.cpp

設定クラスのメンバ関数定義です。ファイルから設定情報を読むようにしています。
見ればわかるように、適当な実装です。

#include "StdAfx.h"
#include "gdcConf.h"

gdcConf::gdcConf(void)
{
   // 設定ファイルから読み込む
   try {
      System::IO::StreamReader^ reader = gcnew System::IO::StreamReader(CONF_FILE_PATH);
      if (reader->EndOfStream == false) {
         // 中身がある
         this->Radiobutton_number = int::Parse(reader->ReadLine());
         this->UsrSavePath = reader->ReadLine();
         System::Diagnostics::Debug::WriteLine(this->UsrSavePath);
      } else {
         // 中身がない
         reader->Close();
         System::Diagnostics::Debug::WriteLine("ファイルの中身がないよー");
         throw gcnew System::IO::FileNotFoundException("ファイルの中身がないよ");
      }
      reader->Close();
   } catch(System::IO::FileNotFoundException^ ex) {
      // 新規作成
      System::IO::File::AppendAllText(CONF_FILE_PATH, DEFAULT_CONF);
   } catch(Exception ^ex) {
      // 本当は必要ない。
      System::Diagnostics::Debug::WriteLine("全ての例外");
   }
}
gdcConf::gdcConf(String^ newSavePath)
{
   // (Path::DirectorySeparatorChar).ToString()
   // 渡された文字列の最後が"\"で終わっていなければ付加する
   if (newSavePath->EndsWith((Path::DirectorySeparatorChar).ToString()) != true) {
      // 最後が"\"でない
      newSavePath = String::Concat(newSavePath, (Path::DirectorySeparatorChar).ToString());
   }
   this->_UsrSavePath = newSavePath;
}

// 設定ファイルへ保存する
void gdcConf::WriteFile(void)
{
//   String^ data = this->Radiobutton_number + "\n" + this->UsrSavePath + "\n";
   System::IO::StreamWriter^ writer = gcnew System::IO::StreamWriter(CONF_FILE_PATH, false);
//   writer->Write(data);
   writer->WriteLine(this->Radiobutton_number);
   writer->WriteLine(this->UsrSavePath);
   writer->Close();
}

Form_SavePlace.h

設定フォーム。役割と名前があっていない悪い例だ。
変更する設定変数は参照渡しを使うので、オーバーロードさせるコンストラクタを追加している。
フォームLOAD時に設定をフォームのデータへ反映させています。

public ref class Form_SavePlace : public System::Windows::Forms::Form
{
public:
   Form_SavePlace(gdcConf^% conf) // 設定は呼び元から入手する
   {
      _conf = conf;
      InitializeComponent();
   };

(略)

private:
   gdcConf^ _conf;
private: System::Void FolderSelect_Click(System::Object^  sender, System::EventArgs^  e) {
   // Show the FolderBrowserDialog.
   System::Windows::Forms::DialogResult result = folderBrowserDialog1->ShowDialog();
   if ( result == ::DialogResult::OK ) {
      this->TextBox_SavePath->Text = this->folderBrowserDialog1->SelectedPath;
      // 復帰と同時にTextBoxの表示は更新される(どこの処理を通っているんだ?)
   }
}
private: System::Void Form_SavePlace_Load(System::Object^  sender, System::EventArgs^  e) {
   // 設定ファイルから有効なチェックボックスを有効にする
   switch (_conf->Radiobutton_number) {
   case TRASH_BOX:
     this->radioButton_UserConf->Checked = false;
     this->radioButton_TrashBox->Checked = true;
     break;
   case USER_CONF:
     this->radioButton_UserConf->Checked = true;
     this->radioButton_TrashBox->Checked = false;
     break;
   default:
     break;
   }
   // パス設定で更新する
   this->TextBox_SavePath->Text = _conf->UsrSavePath;
}

private: System::Void Button_SavePlaceOk_Click(System::Object^  sender, System::EventArgs^  e) {
   // 設定ファイルの更新
   if (this->radioButton_TrashBox->Checked == true) {
     //ゴミ箱
     _conf->Radiobutton_number = TRASH_BOX;
   } else if (this->radioButton_UserConf->Checked == true) {
     //ユーザー設定
     _conf->Radiobutton_number = USER_CONF;
     _conf->UsrSavePath = this->TextBox_SavePath->Text;
   }
   _conf->WriteFile();
   this->Close();
}

private: System::Void Button_SavePlaceCancel_Click(System::Object^  sender, System::EventArgs^  e) {
   this->Close();
}
};

FileTool.h

ファイルを操作をするために作ったクラス。
やっぱ、ヘッダは宣言だけにすべきだ。

public ref class FileTool
{
public:
   FileTool(void);
   void DeleteFoldersAndFiles(void);
   void MoveFoldersAndFiles(String^ targetpath);
private:
   int MoveToTrash(String^ pathname);
   int MoveToFolder(String^srcpath, String^dstpath);
};

FileTool.c

Windows APIを呼び出す実装たち。TCHAR, LPCTSTRとか嫌い。
クラスで作る必要が全くなかった。

#include <windows.h>
#include <string.h>
#include <locale.h>

FileTool::FileTool(void)
{
}
#pragma comment(lib, "shell32.lib")
extern "C" {
int MyDelete(LPCTSTR files2)
{
   SHFILEOPSTRUCT sh_file_operation_struct;
   FILEOP_FLAGS   flags = FOF_ALLOWUNDO  | FOF_NOCONFIRMATION;
   int result = 1;
    // 文字列終端をNULLを2つにする
   LPTSTR files_null = (LPTSTR)calloc(lstrlen(files2) + 2, sizeof(TCHAR));
   lstrcpy(files_null, files2);

   ZeroMemory( &sh_file_operation_struct, sizeof(sh_file_operation_struct) );
   sh_file_operation_struct.wFunc  = FO_DELETE;
   sh_file_operation_struct.fFlags = flags;
   sh_file_operation_struct.pFrom  = files_null;
   result = SHFileOperation(&sh_file_operation_struct);
   free(files_null);
   return result;
}

int MyMoveToFolder(LPCTSTR file, LPCTSTR folder_path) {

   SHFILEOPSTRUCT sh_file_operation_struct;
   int result = 1;
   LPTSTR file_copied = (LPTSTR)calloc(lstrlen(file) + 2, sizeof(TCHAR)); /* files's \0 and \0 */
   LPTSTR file_to = (LPTSTR)calloc(lstrlen(folder_path) + 2, sizeof(TCHAR));
   FILEOP_FLAGS   flags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;

   lstrcpy(file_copied, file);
   lstrcpy(file_to, folder_path);

   ZeroMemory( &sh_file_operation_struct, sizeof(sh_file_operation_struct) );
   sh_file_operation_struct.wFunc  = FO_MOVE;
   sh_file_operation_struct.fFlags = flags;
   sh_file_operation_struct.pFrom  = file_copied;
   sh_file_operation_struct.pTo    = file_to;

   result = SHFileOperation(&sh_file_operation_struct);

   free(file_copied);
   free(file_to);

   return result;
}
};

int FileTool::MoveToTrash(String^ pathname)
{
   int result = 1;
   LPCTSTR files;
  
   //StringをLPCTSTRに変換する
   IntPtr p = System::Runtime::InteropServices::Marshal::StringToHGlobalAuto(pathname);
   files = static_cast<LPCTSTR>(p.ToPointer());
   try {
      result = MyDelete(files);
   } finally {
      System::Runtime::InteropServices::Marshal::FreeHGlobal(p);
   }
   return result;
}
int FileTool::MoveToFolder(String ^srcpath, String ^dstpath)
{
   int result = 1;
   LPCTSTR src, dst;
   //dstpathについて ¥を¥¥に置換する
   String^ folderPath = dstpath->Replace("\\", "\\\\");
   Diagnostics::Trace::WriteLine(folderPath);

   IntPtr p = System::Runtime::InteropServices::Marshal::StringToHGlobalAuto(srcpath);
    src = static_cast<LPCTSTR>(p.ToPointer());
   IntPtr q = System::Runtime::InteropServices::Marshal::StringToHGlobalAuto(folderPath);
    dst = static_cast<LPCTSTR>(q.ToPointer());
   try {
      result = MyMoveToFolder(src, dst);
   } finally {
      System::Runtime::InteropServices::Marshal::FreeHGlobal(p);
      System::Runtime::InteropServices::Marshal::FreeHGlobal(q);
   }
   return result;
}

void FileTool::DeleteFoldersAndFiles(void)
{
   String^ strCategoryFolder = Environment::GetFolderPath(Environment::SpecialFolder::DesktopDirectory);
   array<String^>^ aryFileNames = IO::Directory::GetFiles(strCategoryFolder);
   for each (String^ strFileName in aryFileNames) {
         Diagnostics::Trace::WriteLine(strFileName);
//      IO::File::Delete(strFileName);
         MoveToTrash(strFileName);
      }

   array<String^>^ aryFolderNames = System::IO::Directory::GetDirectories(strCategoryFolder);//
   for each (String^ strSubFolderName in aryFolderNames) {
         Diagnostics::Trace::WriteLine(strSubFolderName);
         MoveToTrash(strSubFolderName);
//      IO::Directory::Delete(strSubFolderName, true);
      }
}

void FileTool::MoveFoldersAndFiles(String^ targetpath)
{
   // targetpathの最後に ¥ がついている必要がある
   String^ strCategoryFolder = Environment::GetFolderPath(Environment::SpecialFolder::DesktopDirectory);
   array<String^>^ aryFileNames = IO::Directory::GetFiles(strCategoryFolder);
   for each (String^ strFileName in aryFileNames) {
         MoveToFolder(strFileName, targetpath);

      }

   array<String^>^ aryFolderNames = System::IO::Directory::GetDirectories(strCategoryFolder);
   for each (String^ strSubFolderName in aryFolderNames) {
         MoveToFolder(strSubFolderName, targetpath);
      }
}