今回はFileNameCleanerの最終回として、FileNameCleanerProc.hの解説をします。
SkeltonWizardが生成するBCCSkeltonコードで、開発者が行いたい処理はほぼこの(プロジェクト名)Pron.hに書き込まれますので、このプログラムでも、ダイアログの二つのボタンを押したときの処理と共に、ここに次の処理を行うユーザー定義関数を書いています。
(1)ファイルパス、名の文字列にUnicodeの一般句読点符号(U+2000~U+206F)のコードがあるか否かをチェックし、あればこれを除去した文字列を生成する。→CheckName(WCHAR*);
(2)フォールダー内のファイルを列挙する。→CheckFiles(WCHAR*);
(3)オリジナルのファイルパス、名を除去後のファイルパス、名に変更する。→ShellRename(WCHAR*, WCHAR*);
なお、このプログラムは自動生成コード以外は全てワイド文字(WCHAR)で書いていますので、該当部分は色付けをします。
【FileNameCleanerProc.h】
//////////////////////////////////////////
// FileNameCleanerProc.h
// Copyright (c) 05/06/2020 by BCCSkelton
//////////////////////////////////////////
/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnInit(WPARAM wParam, LPARAM lParam) {
//クライアントエリアサイズを記録
RECT rec;
GetClientRect(m_hWnd, &rec);
m_Width = rec.right - rec.left;
m_Height = rec.bottom - rec.top;
return TRUE;
}
//(解説:ダイアログサイズを変更した際にコントロールを移動させるための基礎となる、ダイアログ生成時のクライアントエリアサイズをメンバー変数に記録します。)
bool CMyWnd::OnSize(WPARAM wParam, LPARAM lParam) {
//コントロールの位置、サイズ変更
RECT rec; //矩形取得用
POINT pt; //スクリーン座標変換用
int w, h; //幅、高さ計算用
int diffx = LOWORD(lParam) - m_Width; //前回と今回の差分
int diffy = HIWORD(lParam) - m_Height; //前回と今回の差分
m_Width = LOWORD(lParam); //今回の幅
m_Height = HIWORD(lParam); //今回の高さ
//左右移動-IDC_SELECT
GetWindowRect(GetDlgItem(m_hWnd, IDC_SELECT), &rec); //ウィンドウ位置取得
w = rec.right - rec.left;
h = rec.bottom - rec.top;
pt.x = rec.left;
pt.y = rec.top;
ScreenToClient(m_hWnd, &pt);
MoveWindow(GetDlgItem(m_hWnd, IDC_SELECT), pt.x + diffx, pt.y, w, h, TRUE);
//幅変更-IDC_EDIT
GetWindowRect(GetDlgItem(m_hWnd, IDC_EDIT), &rec); //ウィンドウ位置取得
w = rec.right - rec.left;
h = rec.bottom - rec.top;
pt.x = rec.left;
pt.y = rec.top;
ScreenToClient(m_hWnd, &pt);
MoveWindow(GetDlgItem(m_hWnd, IDC_EDIT), pt.x, pt.y, w + diffx, h, TRUE);
//幅高さ変更-IDC_FILELIST
GetWindowRect(GetDlgItem(m_hWnd, IDC_FILELIST), &rec); //ウィンドウ位置取得
w = rec.right - rec.left;
h = rec.bottom - rec.top;
pt.x = rec.left;
pt.y = rec.top;
ScreenToClient(m_hWnd, &pt);
MoveWindow(GetDlgItem(m_hWnd, IDC_FILELIST), pt.x, pt.y, w + diffx, h + diffy, TRUE);
//左右上下移動-IDOK
GetWindowRect(GetDlgItem(m_hWnd, IDOK), &rec); //ウィンドウ位置取得
w = rec.right - rec.left;
h = rec.bottom - rec.top;
pt.x = rec.left;
pt.y = rec.top;
ScreenToClient(m_hWnd, &pt);
MoveWindow(GetDlgItem(m_hWnd, IDOK), pt.x + diffx, pt.y + diffy, w, h, TRUE);
//クライアントエリアを再描画
InvalidateRect(m_hWnd, NULL, TRUE);
return TRUE;
}
//(解説:ここがダイアログのサイズが変更された際(WM_SIZEメッセージ)に呼ばれる処理で、①最初のサイズと変更後のサイズの差分を取り、変更後のサイズを記録し、②次に縦横に移動したり、サイズ変更するコントロールのウィンドウサイズにその差分を反映して新しいウィンドウ位置、サイズに変更します。)
bool CMyWnd::OnClose(WPARAM wPram, LPARAM lParam) {
//終了確認
if(MessageBoxW(m_hWnd, L"終了しますか", L"終了確認",
MB_YESNO | MB_ICONINFORMATION) == IDYES) {
//処理をするとDestroyWindow、PostQuitMessageが呼ばれる
return TRUE;
}
else
return FALSE;
}
bool CMyWnd::OnDestroy(WPARAM wPram, LPARAM lParam) {
//ダイアログベースの場合はこれが必要
PostQuitMessage(0);
return TRUE;
}
//(解説:SkeltonWizardの作成したままの内容です。)
bool CMyWnd::OnMinMax(WPARAM wParam, LPARAM lParam) {
//典型的なウィンドウのサイズ制限処理
MINMAXINFO *pmmi;
pmmi = (MINMAXINFO*)lParam;
pmmi->ptMinTrackSize.x = MINW + 16; //クライアントエリア + 16
pmmi->ptMinTrackSize.y = MINH + 39; //クライアントエリア + 61
return FALSE; //処理はDefWndProcに任す
}
//(解説:サイズ変更時の最小サイズ処理です。)
/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CMyWnd::OnSelect() {
//ファイル名クリーニングを行うフォールダーの選択
WCHAR* fp = g_Cmndlg.GetPath(m_hWnd,
L"ファイル名クリーニングを行うフォールダーの選択");
//パス名を記録
if(fp) {
lstrcpyW(g_Path, fp);
SendMessageW(GetDlgItem(m_hWnd, IDC_EDIT), WM_SETTEXT, 0, (LPARAM)g_Path);
}
else {
MessageBoxW(m_hWnd, L"キャンセルされました", L"エラー",
MB_OK | MB_ICONERROR);
return FALSE;
}
//フォールダー名のチェック
if(CheckName(g_Path)) {
MessageBoxW(m_hWnd, L"フォールダー名をチェックしました。\n"\
"続いてフォールダー内のファイルをチェックします。",
L"成功", MB_OK | MB_ICONINFORMATION);
SendMessageW(GetDlgItem(m_hWnd, IDC_EDIT), WM_SETTEXT, 0, (LPARAM)g_Cleaned);
lstrcpyW(g_Path, g_Cleaned); //g_Pathに修正版を記録
}
else {
MessageBoxW(m_hWnd, L"フォールダー名のクリーニングに失敗しました。",
L"失敗", MB_OK | MB_ICONERROR);
return FALSE;
}
//ファイル名のチェック
if(CheckFiles(g_Path)) {
SendMessageW(GetDlgItem(m_hWnd, IDC_FILELIST), WM_SETTEXT, 0, (LPARAM)g_OriginalList.ToChar());
MessageBoxW(m_hWnd, L"これらファイル名をチェックしました。\n"\
"最終ファイル名を表示します。",
L"成功", MB_OK | MB_ICONINFORMATION);
SendMessageW(GetDlgItem(m_hWnd, IDC_FILELIST), WM_SETTEXT, 0, (LPARAM)g_CleanedList.ToChar());
return TRUE;
}
else {
MessageBoxW(m_hWnd, L"ファイル名のクリーニングに失敗しました。",
L"失敗", MB_OK | MB_ICONERROR);
return FALSE;
}
}
//(解説:クリーニング処理を行うフォールダーを「フォールダーを選ぶ」ダイアログで選択し、先ずフォールダー名をチェックし、一般句読点符号があればそれを除去し、次にフォールダー内のファイルをチェックして同様の処理をします。ワイド文字対応の部分は紫文字を見てください。'L'を付けた定数や'W'が付いた関数がワイド文字対応です。)
bool CMyWnd::OnOk() {
SendMsg(WM_CLOSE, 0, 0);
return TRUE;
}
///////////////////////////////
//ユーザー関数定義
//CheckName - フォールダー名、
//ファイル名に禁則文字が入って
//いるかチェックする
//無ければTRUEを返し、あれば
//削除結果を返す(TRUE、FALSE)
///////////////////////////////
bool CMyWnd::CheckName(WCHAR* name) {
lstrcpyW(g_Original, name);
//Unicodeの一般句読点(U+2000 - U+206F)がファイル名に含まれていれば、除去してRenameする
int taboo = 0;
WCHAR* sp = g_Original;
WCHAR* dp = g_Cleaned;
while(*sp != L'\0'){
if(*sp >= 0x2000 && *sp <= 0x206F) { //(解説:WCHARはwchar_tのことで、実体はunsigned short integer(2バイト)です。)
sp++;
taboo++;
}
*dp++ = *sp++; //g_Originalを修正してg_Cleanedへコピー
}
*dp = *sp; //NULL終端をコピー
WCHAR mes[MAX_PATH];
if(taboo) {
wsprintfW(mes, L"Unicodeの一般句読点(U+2000 - U+206F)文字が%d個見つかりました。削除しますか?", taboo);
if(MessageBoxW(m_hWnd, mes, L"確認", MB_YESNO | MB_ICONQUESTION) == IDYES) {
if(!ShellRename(g_Original, g_Cleaned))
return FALSE;
}
else {
MessageBoxW(m_hWnd, L"Unicodeの一般句読点(U+2000 - U+206F)文字が残ったままです", L"警告",
MB_YESNO | MB_ICONQUESTION);
return FALSE;
}
}
return TRUE;
}
//(解説:外部変数のオリジナルファイルパス、名(g_Original)をWCHAR単位でチェックし、一般句読点符号があればその個数をtabooに記録し、それをスキップした文字列をg_Cleanedに記録します。そしてg_Originalのファイル名をg_Cleanedに変更します。)
///////////////////////////////
//ユーザー関数定義
//GetFileName - 1 フォールダーの
//ファイル名を列挙して、禁則文字
//が入っているかチェックする
///////////////////////////////
bool CMyWnd::CheckFiles(WCHAR* path) {
//g_OriginalList、g_CleanedListの初期化
g_OriginalList = L"";
g_CleanedList = L"";
//フォールダー内ファイルのチェック
CSTRW fl(path);
fl = fl + L"\\*.*";
WIN32_FIND_DATAW fd;
HANDLE hFind = FindFirstFileW(fl.ToChar(), &fd);
if (hFind == INVALID_HANDLE_VALUE) {
MessageBoxW(m_hWnd, L"ファイル情報取得失敗", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
else {
while(FindNextFileW(hFind, &fd)) {
//フォールダの場合は何もしない
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
//ファイルの場合、g_Originalにファイル名をコピーしてCheckNameに掛ける
else {
//オリジナルリストの作成
g_OriginalList = g_OriginalList + path;
g_OriginalList = g_OriginalList + L"\\";
g_OriginalList = g_OriginalList + fd.cFileName;
g_OriginalList = g_OriginalList + L"\r\n";
//Unicodeの一般句読点除去と修正版リストの作成
fl = path;
fl = fl + L"\\";
fl = fl + fd.cFileName;
if(CheckName(fl.ToChar())) {
g_CleanedList = g_CleanedList + g_Cleaned;
g_CleanedList = g_CleanedList + L"\r\n";
}
else
MessageBoxW(m_hWnd, L"Unicodeの一般句読点除去に失敗しました", g_Original,
MB_OK | MB_ICONEXCLAMATION);
}
}
}
FindClose(hFind);
return TRUE;
}
//(解説:指定フォールダー内のファイルを列挙し、一つづつファイル名チェックと一般句読点符号の除去を行います。)
//////////////////////////////////////////////
//ユーザー関数定義
//ShellExecuteExを起動し、プロセスの終了を待つ
//////////////////////////////////////////////
bool CMyWnd::ShellRename(WCHAR* original, WCHAR* clean) {
//SHFileOperationを使う
SHFILEOPSTRUCTW fop;
ZeroMemory(&fop, sizeof(SHFILEOPSTRUCTW));
fop.wFunc = FO_RENAME;
//wFunc-実行したい機能を指定
//FO_COPY コピー
//FO_DELETE 削除
//FO_MOVE 移動
//FO_RENAME 名前の変更
fop.pFrom = original;
fop.pTo = clean;
if(!SHFileOperationW(&fop)) {
MessageBoxW(m_hWnd, L"SHFileOperationで名前変更しました", L"成功", MB_OK | MB_ICONINFORMATION);
return TRUE;
}
else {
MessageBoxW(m_hWnd, L"SHFileOperationの名前変更に失敗しました", L"失敗", MB_OK | MB_ICONERROR);
return FALSE;
}
}
//(解説:Shellサービスの「万能」ファイル処理関数です。これ一つあればExplorerの行うファイル処理は全てできますね。)
ということで、生まれて初めて(止むに止まれぬ事情から)Windowsが使うワイド文字(Unicode UTF-16)だけを使ってプログラムを書いてみました。前に書いたように次の点に気を付ければ、ASCIIベースで書くのと大して変わらずに書けますね。
(1)文字列は8バイトのchar型から16バイトWCHAR(wchar_t)型に替える。その為にwchar.hをインクルードする。
(2)今まで使っていた8バイト用のWin32API関数や構造体は(ワイド文字用のものが必ずあるので)ワイド文字用のものを使う。探す際のヒントは、以下の通りです。
①(関数名)→(関数名)W や"str"等の文字列を意味する文字が入っている関数名であれば"strW"に置き換えて検索する。
②(構造体名)→(構造体名)Wや①と同様に文字列を彷彿させる名称にWを付加したものを検索する。
(3)文字列定数は"なんちゃら"→L"なんちゃら"とする。単一文字の場合も'〇'→L'〇'とする。
(4)文字列のヌル終端はNULL(8バイト)でなく、明示的に16バイトのL'\0'とする。
しかし、このプログラムを書いて感じたことは、「確かにMicrosoft Visual C++(今のVisual Studio)を使う際に、簡単にASCIIベースと、ワイド文字(UTF-16、Microsoftはこれを"Unicode"というのですが...)ベースでスイッチできるTCHARを使って書くのは便利ですが、それによって『自分が今正確に何をしているのか分からなくなる』悪影響が出ると不味」だな、ということです。それでやっと前に引用したウェブの記事(TCHAR はもう使うな)の正しい意味が体感出来ました。
さて、それではもうUnicodeとは手を切って、自分の世界(「Unicodeには触るな!」)に戻ろう、と思いましたが、CSTRWのファイル操作対象がUTF-16だけ、というのが妙に引っかかってます。まぁ、PCでWindowsを使う限り、先ずUTF-7等の「はぐれUTF」や矢鱈メモリーを喰う「UTF-32」は無視してもよいので「リトルエンディアンのUTF-16+BOM」を基本とすることは問題ありませんが、現在Unicodeで主流となり、「どのエディター(Microsoftの「メモ帳」もWin10からUTF-8対応だそうです)でもUTF-8を読めるらしいのでBCCSkeltonだけ無視したら不味かろう」という想いと、Win32API(windows.h)にUTF-8も扱えるWideCharToMultiByte とMultiByteToWideCharという関数があることが分かりましたので、ちょっと機能拡張してみようかと思います。
では次回!

















