最後にResizerProc.hを見てみましょう。

 

//////////////////////////////////////////
// ResizerProc.h
// Copyright (c) 03/27/2022 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCRESIZERの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CRESIZER::OnInit(WPARAM wParam, LPARAM lParam) {

    //ワイド文字使用のためロケールの初期化(日本語)
    setlocale(LC_CTYPE, "JPN");
    //GDI+の開始
    GdiplusStartup(&m_gdiToken, &m_gdiSI, NULL);

//(解説:前にも書きましたが、GDI+を利用し始める前の手続きはコンストラクターやWM_CREATE、WM_INITDIALOGで行います。)

    //リストビューウィンドウハンドル
    m_ListView.m_hWnd = GetDlgItem(m_hWnd, IDC_LISTVIEW);
    //リストビュー親ウィンドウハンドル
    m_ListView.m_hParent = m_hWnd;
    //リストビューインスタンス
    m_ListView.m_hInstance = m_hInstance;
    //リストビューコントロールID
    m_ListView.m_hMenu = (HMENU)IDC_LISTVIEW;

//(解説:ここまではCLISTVIEWクラスのm_ListViewがダイアログコントロールのリストビューを包含できるように必要なパラメーターを取得します。)

    //リストビュー列名と幅の設定
    m_ListView.InsertColumn(0, 30, "No", LVCFMT_CENTER);
    m_ListView.InsertColumn(1, 184, "ファイル名");
    m_ListView.InsertColumn(2, 100, "幅");
    m_ListView.InsertColumn(3, 100, "高さ");
    m_ListView.InsertColumn(4, 100, "修正幅");
    m_ListView.InsertColumn(5, 100, "修正高さ");
    //リストビュー拡張スタイルの設定
    INITCOMMONCONTROLSEX ic;
    ic.dwSize = sizeof(ic);
    ic.dwICC = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx(&ic);
    DWORD lvStyle = SendItemMsg(IDC_LISTVIEW, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
    lvStyle |= LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES;
    SendItemMsg(IDC_LISTVIEW, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, lvStyle);
//(解説:次にリストビュ―コントロールの初期化をm_ListViewを使って行います。ここでは6つの列、そのタイトルを設定し、表示に枠線を入れて、選択をセルではなく、列にしています。)

    return TRUE;
}

/////////////////////////////////
//主ウィンドウCRESIZERの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CRESIZER::OnSelsrc() {

    char* cp = g_Cmndlg.GetPath(m_hWnd, "ソースファイルパスの選択");
    if(!cp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;パス
    }
    //ソースファイルパス(最後の'\'付)
    m_SrcPath = cp;
    m_SrcPath = m_SrcPath + "\\";
    SendItemMsg(IDC_SOURCE, WM_SETTEXT, 0, (LPARAM)m_SrcPath.ToChar());

//(解説:ソースファイルパスをフォールダー選択ダイアログで選択します。)

   //リストビューにファイルを列挙
    SetFileNames(cp);

//(解説:そのフォールダーにあるGDI+対象ファイルを列挙します。)

    return TRUE;
}

bool CRESIZER::OnOverwrite() {

    switch(SendItemMsg(IDC_OVERWRITE, BM_GETCHECK, 0, 0)) {
    case BST_CHECKED:
        //ソースの内容をデスティネーションにコピー
        m_DstPath = m_SrcPath;
        //ディスティネーションエディットボックスに表示
        SendItemMsg(IDC_DIST, WM_SETTEXT, 0, (LPARAM)m_DstPath.ToChar());
        //デスティネーション選択ボタンを無効化
        EnableWindow(GetDlgItem(m_hWnd, IDC_SELDIST), FALSE);
        break;
    case BST_UNCHECKED:
        //デスティネーションエディットボックスを消去
        SendItemMsg(IDC_DIST, WM_SETTEXT, 0, (LPARAM)"");
        //ディスティネーションファイルパスクリア
        m_DstPath = "";
        //デスティネーション選択ボタンを有効化
        EnableWindow(GetDlgItem(m_hWnd, IDC_SELDIST), TRUE);
        break;
    default:
        return FALSE;
    }

//(解説:「上書き」チェックボックスが選択された場合の処理です。内容はコメント通りです。)

    return TRUE;
}

bool CRESIZER::OnSeldist() {

    char* cp = g_Cmndlg.GetPath(m_hWnd, "コピー先フォールダーの選択");
    if(!cp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //ディスティネーションファイルパス(最後の'\'付)
    m_DstPath = cp;
    m_DstPath = m_DstPath + "\\";
    SendItemMsg(IDC_DIST, WM_SETTEXT, 0, (LPARAM)m_DstPath.ToChar());

//(解説:コピーの場合のディスティネーションフォールダー選択処理です。)

    return TRUE;
}

bool CRESIZER::OnIdgo() {

 

//(解説:以下は「実行」ボタンが押された場合の処理です。)

    //縮小コピー処理数
    m_Noc = 0;    //(解説:毎回0初期化します。)

    //作業用文字列バッファ
    char buff[MAX_PATH];
    //ソースファイルフォールダーの取得チェック
    if(!*m_SrcPath.ToChar()) {

//(解説:これをしないと落ちます。)

        MessageBox(m_hWnd, "ソースファイルパスが未入力です", "エラー",
                    MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //一辺の最大値を取得チェック
    SendItemMsg(IDC_MAX, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
    if(!*buff || !IsDigitStr(buff)) {

//(解説:IsDigitStrは、CのIsDigit関数の文字列版です。)

        MessageBox(m_hWnd, "一辺の最大値が未入力または不正です", "エラー",
                    MB_OK | MB_ICONERROR);
        return FALSE;
    }
    m_Max = atoi(buff);

//(解説:m_Maxに一辺の最大値が入ります。)

    //ディスティネーションファイルフォールダーの取得チェック
    if(!*m_DstPath.ToChar()) {
        MessageBox(m_hWnd, "ディスティネーションファイルパスが未入力です", "エラー",
                    MB_OK | MB_ICONERROR);

//(解説:上書きの場合もソースファイルパスがコピーされていますので。)

        return FALSE;
    }
    //上書き(TRUE)か否かの確認
    bool overwrite = (SendItemMsg(IDC_OVERWRITE, BM_GETCHECK, 0, 0) == BST_CHECKED);
    //リストビューのファイル名でイメージをコピーする
    for(int i = 0; i < m_NoOfFiles; i++)
        if(CopyImages(i, overwrite))    //(解説:↑で採ったoverwriteフラグを使います。)

            m_Noc++;    //(解説:処理数です。)

    //終了通知
    wsprintf(buff, "条件を満たした%d個のファイルを縮小処理しました。", m_Noc);
    MessageBox(m_hWnd, buff, "終了", MB_OK | MB_ICONINFORMATION);
    return TRUE;
}

bool CRESIZER::OnIdok() {

    EndModal(m_Noc);    //(解説:処理数を返します。)

    return TRUE;
}

bool CRESIZER::OnIdcancel() {

    EndModal(FALSE);
    return TRUE;
}

bool CRESIZER::OnClose(WPARAM wParan, LPARAM lParam) {

    GdiplusShutdown(m_gdiToken);

//(解説:GDI+の開始処理をWN_INITDIALOGで行ったので、終了処理をここで行います。)

    return TRUE;
}

//////////////////////////////////
//ユーザー定義関数 - SetFileNames
//・pathで指定するフォールダー内の
// ファイル名をリストビューに登録
//・m_NoOfFilesにファイル数を代入
//・エラーまたはファイルが無い場合
// FALSEを返す
//////////////////////////////////
bool CRESIZER::SetFileNames(char* path) {

    //リストビューをクリアーし、ファイル数を0にする
    m_ListView.DeleteAllItems();
    m_NoOfFiles = 0;

//(解説:毎回初期化されます。)

    //ファイル検索
    char buff[MAX_PATH];            //作業用文字列バッファ
    CSTR FileName(path);
    FileName = FileName + "\\*.*";    //ファイル名検索用パス + ワイルドカード

//(解説:以下のFindFirstFile、FindNextFileを使う際の検索ファイル名はワイルドカードでなくてはなりません。)

    WIN32_FIND_DATA fd;
    HANDLE hFind = FindFirstFile(FileName.ToChar(), &fd);
    if(hFind == INVALID_HANDLE_VALUE) {
        MessageBox(m_hWnd, "ファイル情報取得失敗", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    else {
        do {
            //ディレクトリーの場合何もしない
            if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
            //ファイルの場合
            else {
                //GDI+の対象となる画像ファイルの場合
                if(g_ExtChk.CheckExt(fd.cFileName, IMGFILTER)) {

//(解説:ここでCEXTCHKクラスのCheckExt関数を使うことでGDI+対象ファイルか否かをチェックします。)

                    //リストボックスに表示(番号)
                    wsprintf(buff, "%d", m_NoOfFiles);
                    m_ListView.InsertItem(m_NoOfFiles, buff, -1, m_NoOfFiles);
                    //ファイル名
                    m_ListView.SetItem(m_NoOfFiles, 1, fd.cFileName);
                    m_NoOfFiles++;

//(解説:ファイル番号とファイル名をリストボックスに入れます。)

                }
            }
        } while(FindNextFile(hFind, &fd));
    }
    FindClose(hFind);
    return TRUE;
}

//////////////////////////////////
//ユーザー定義関数 - IsDigitStr
//・strで指定する文字列が'0' -'9'
// 迄の数字文字か否かチェックする
//・そうであればTRUE、数字以外があ
// ればFALSEを返す
//////////////////////////////////
bool CRESIZER::IsDigitStr(char* str) {

    for(char* cp = str; *cp; cp++) {
        if(*cp > '9' || *cp < '0')
            return FALSE;
    }

//(解説:IsDigit関数と同じことを文字列の各文字に対して行います。)

    return TRUE;
}

/////////////////////////////////////////////////
//ユーザー定義関数 - CopyImages
//・m_ListViewの引数n行、第2列のファイル名を取得
//・m_SrcPath + ファイル名の画像インスタンスを生
// 成して、幅、高さを取得
//・m_Max と比較して、一辺がそれより大きければ長
// い辺をm_Max とし、短い辺を画像の縦横比に応じ
// て修正
//・修正幅、高さのビットマップフレームを生成し、
// そこに縮小画像を書き込む
//・書き込まれたビットマップを、オリジナル画像の
// エンコーダーを用いてm_DstPath + ファイル名で
// 書き込む
//・「上書き」モードの場合は、ファイル名を"*.bak"
// とし、オリジナルを削除、新ファイルの名前をオ
// リジナルのものに変える
/////////////////////////////////////////////////
bool CRESIZER::CopyImages(int n, bool overwrite) {

 

//(解説:以下はGDI+のイメージコピー処理です。既にAlbumで説明していますね。)

   //変数宣言
    int Width;                        //イメージの幅
    int Height;                        //イメージの高さ
    int AdjW;                        //修正後幅
    int AdjH;                        //修正後高さ
    CSTR SrcFile, DstFile;            //ソースファイル名、書き込みファイル名
    WCHAR WFName[MAX_PATH];            //フルパスファイル名用ワイド文字列(WCS)
    char name[MAX_PATH];            //リストボックスのファイル名
    char buff[MAX_PATH];            //作業用文字列バッファ
    //リストビューから読み込みファイル名を取得
    m_ListView.GetItemText(n, 1, name, MAX_PATH);
    //読み込みファイル名の作成
    SrcFile = m_SrcPath;
    SrcFile = SrcFile + name;                //m_SrcPathは既に'\'付

//(解説:ここまででソースファイル名を準備します。)

    //書き込みファイル名の作成
    if(overwrite)                            //nameの拡張子を"bak"に変える
        lstrcpy(strstr(name, "."), ".bak");    //g_ChkExtを経由しており'.'がある
    DstFile = m_DstPath;
    DstFile = DstFile + name;                //m_DstPathは既に'\'付
//(解説:ここまででディスティネーションファイル名を準備します。上書きの場合の拡張子はbakです。)

 //Imageインスタンスの生成(ファイル名はワイド文字化)
    mbstowcs(WFName, SrcFile.ToChar(), MAX_PATH);
    m_pImage = new (Image)(WFName);
//(解説:GDI+では必ずワイド文字を使います。)

    //イメージの幅、高さを取得
    Width  = m_pImage->GetWidth();
    Height = m_pImage->GetHeight();

//(解説:やっと幅と高さが求められます。)

    //リストボックスの表示(1)
    wsprintf(buff, "%d", Width);    //イメージ幅
    m_ListView.SetItem(n, 2, buff);
    wsprintf(buff, "%d", Height);    //イメージ高さ
    m_ListView.SetItem(n, 3, buff);

//(解説:リストボックスに書き込みます。)

    //一片の最大値から原寸縦横比のまま修正幅、高さを算出
    if(Width > m_Max || Height > m_Max) {
        if(Width > Height) {
            AdjH = Height * ((double)m_Max / (double)Width);
            AdjW = m_Max;
        }
        else {
            AdjW = Width * ((double)m_Max / (double)Height);
            AdjH = m_Max;
        }
    }

    //縦横共にm_Maxを超えない場合はFALSEで戻る
    else {
        if(!overwrite)    //上書きでなければ
            //条件に適合しないファイルはそのままコピーする
            CopyFile(SrcFile.ToChar(), DstFile.ToChar(), FALSE);
        delete m_pImage;
        return FALSE;
    }

//(解説:原寸縦横比の処理もAlbumと同じですが、上書きでない場合、一辺の最大値以下の画像ファイルもコピーします。)

    //リストボックスの表示(2)
    wsprintf(buff, "%d", AdjW);        //修正幅
    m_ListView.SetItem(n, 4, buff);
    wsprintf(buff, "%d", AdjH);        //修正高さ
    m_ListView.SetItem(n, 5, buff);

//(解説:リストビューに修正した幅、高さを書き込みます。従って書き込まれたファイルが処理されたことになります。)

    //縮小画像用のBitmapオブジェクトをオリジナルの縦横比で生成
    Bitmap* pBitmap = new Bitmap(AdjW, AdjH);
    //Bitmapオブジェクトから出力用のGraphicsフレームオブジェクトを作成
    Graphics* pFrame = Graphics::FromImage(pBitmap);
    //Rectオブジェクトのフレームの矩形を生成
    Gdiplus::Rect rtFrame(0, 0, AdjW, AdjH);
    //画像の全体をフレームのrtFrameで表した矩形領域へ描画
    pFrame->DrawImage(m_pImage, rtFrame, 0, 0, Width, Height, UnitPixel);    //1ポイント == 1/72インチ

//(解説:縮小イメージの書き込み処理もAlbumと同じです。ビットマップに書き込むだけです。)

   //書き込み用エンコーダのCLSIDを取得
    int i = g_ExtChk.CheckExt(SrcFile.ToChar(), IMGFILTER);                    //画像形式をチェック

//(解説:ここでCEXTCHKクラスの活躍です。エンコーダー型をソースファイル名からチェックします。)

    if(!i) {    //(解説:該当がなかった場合です。)

        MessageBox(m_hWnd, "対象ファイルではありません", "エラー", MB_OK | MB_ICONERROR);
        //ビットマップのメモリーを解放
        delete pBitmap;
        //m_Imageインスタンスを解放
        delete m_pImage;
        return FALSE;
    }
    else if(i > 2)    //(解説:jpegファイルが、jpegとjpgの二種類の表記があるからです。)

        i--;

//(解説:。)

    CLSID encoderClsid;
    //g_EncoderList配列のエンコーダー型を使ってCLSIDを取り出す
    if(GetEncoderClsid(g_EncoderList[i - 1], &encoderClsid) == -1) {

//(解説:このCEXTCHKクラスのCheckExt関数で採った値で、外部エンコーダー型配列に適用します。)

        MessageBox(m_hWnd, DstFile.ToChar(), "エンコーダーCLSIDの取得失敗", MB_OK | MB_ICONERROR);
        //ビットマップのメモリーを解放
        delete pBitmap;
        //m_Imageインスタンスを解放
        delete m_pImage;
        return FALSE;
    }
    //EncoderParameters配列の第1オブジェクトを使って品質を指定
    LONG lQuality = 100;    //品質レベルは100の最高品質
    //EncoderParameters配列オブジェクト
    EncoderParameters EncoderParams;
    //先頭のEncoderParameterオブジェクト
    EncoderParams.Parameter[0].Guid = EncoderQuality;                    //品質であることを指定
    EncoderParams.Parameter[0].NumberOfValues = 1;                        //Valueの変数数は1つ
    EncoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;    //Valueの変数の長さ
    EncoderParams.Parameter[0].Value = (VOID*) &lQuality;                //品質指定
    EncoderParams.Count = 1;                                            //EncoderParameters配列は一つだけ
    //書き込みファイル名のワイド文字版を作る
    mbstowcs(WFName, DstFile.ToChar(), MAX_PATH);
    //フレームとして使ったBitmapを指定エンコーダー、品質でファイルに保存
    pBitmap->Save(WFName, &encoderClsid, &EncoderParams);

//(解説:最後に品質レベルを指定して、ディスティネーションファイル名をワイド文字にしてファイルを書き込みます。)

    //ビットマップのメモリーを解放
    delete pBitmap;
    //m_Imageインスタンスを解放
    delete m_pImage;

//(解説:newでつくったインスタンスは必ず消しましょう。)

    //上書き処理
    if(overwrite) {
        DeleteFile(SrcFile.ToChar());                    //オリジナルファイルを削除
        MoveFile(DstFile.ToChar(), SrcFile.ToChar());    //縮小ファイルをオリジナル名に変更

//(解説:ImageクラスのSave関数は上書きができないので、bakで書き込んで、オリジナルを削除して、bakを変更することで上書きしています。)

    }
    return TRUE;
}

////////////////////////////////////////////////////
//ユーザー定義関数
//Image CODEC CLSIDの取得(出展:Microsoft Doc)
//formatに合致するCODECのCLSIDアドレスをpClsidに渡す
//戻り値:成功-Image CODEC配列(ImageCodecInfo)引数
//    失敗- -1
////////////////////////////////////////////////////
int CRESIZER::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {

    //イメージCODECの数、サイズ取得
    UINT  num = 0;        //image encoderの数
    UINT  size = 0;        //image encoder配列のサイズ(バイト数)
    GetImageEncodersSize(&num, &size);
    if(size == 0) {
        return -1; //イメージCODECサイズ取得失敗
    }
    //イメージCODEC情報用クラス(ImageCodecInfo)
    ImageCodecInfo* pImageCodecInfo = NULL;
    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo == NULL) {
        return -1; //メモリ確保失敗
    }
    //イメージCODEC情報クラス取得
    GetImageEncoders(num, size, pImageCodecInfo);

    for(UINT j = 0; j < num; ++j) {
        //formatがCODECのMIME(Multipurpose Internet Mail Extensions)型なら
        if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
            //J番目のCODECのグローバル一意識別子(CLSID)を渡す
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;    //成功-
        }  
    }
    free(pImageCodecInfo);
    return -1;            // 失敗 - 要求した種類のエンコーダが存在しなかった
}

//(解説:これは前にも書きましたが、Microsoft Docのコードをベースにちょっと手を入れたエンコーダーCLSID取得関数です。)

 

これでResizerの解説は終了です。スタンドアローンのプログラム(exeファイル)、DLL(dllファイル)いずれもBatchGoodでコンパイルできます。試してみてください。

 

今回からはResizerのコード部分です。

先ずはcppから。実はこのcppファイル最初はモーダルダイアログのスタンドアロンで作成し、後でDLLにしています。なので、二つ紹介します。

 

【Resizer.cpp】

//////////////////////////////////////////
// Resizer.cpp
//Copyright (c) 03/27/2022 by BCCSkelton
//////////////////////////////////////////
#include    "Resizer.h"
#include    "ResizerProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //2重起動防止
    if(!Resizer.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)
        HWND hWnd = FindWindow("MainWnd", "Resizer");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }
    //メインモーダルダイアログを起動
    return Resizer.DoModal(NULL, "IDD_MAIN", ModalProc, hInstance);    //(解説:ウィンドウ部分はここだけ。)
}

/* DLLで使用するには以下をつかう
#include    "DLL.h"    //(解説:これは前に紹介しているEXPORTとIMPORTの入ったヘッダーです。)
#include    "Resizer.h"
#include    "ResizerProc.h"

////////////////
// DLLMain関数
////////////////
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpReserved) {

    //CFILELISTクラスメンバー変数の初期化
    Resizer.m_hInstance = hInstance;
    return TRUE;
}

///////////////////
// CopyImages関数
///////////////////
EXPORT int ResizeImages() {

    //メインモーダルダイアログを起動
    return Resizer.DoModal(NULL, "IDD_MAIN", ModalProc, Resizer.m_hInstance);
}
*/

非常に簡単ですね。次はResizerのヘッダー部分を。

 

【Resizer.h】

//////////////////////////////////////////
// Resizer.h
// Copyright (c) 03/27/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResResizer.h"
//ワイド文字使用の為"wchar.h"をインクルード
#include <wchar.h>
//GDI+のインクルード、ネームスペースの使用
#include    <gdiplus.h>
using namespace Gdiplus;

//(解説:ここはDGI+を使う際の定番手続きですね。)

//イメージファイルのフィルター
#define        IMGFILTER    "イメージファイル\0*.bmp;*.jpeg;*.jpg;*.gif;*.tiff;*.png;*.wmf;*.emf;*.ico\0\0"
//エンコーダーリスト
const WCHAR* g_EncoderList[5] = {L"image/bmp", L"image/jpeg", L"image/gif", L"image/tiff", L"image/png"};

//(解説:GDI+で扱えるファイルを「ファイルを開くダイアログ」のフィルターの形にして纏めておくとCEXTCHKクラスが使えるのと、それに合わせたエンコーダーリスト(配列)を持っておくと処理が簡単になります。)


/////////////////////////////////////////////////////////////////////
//CRESIZERクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CRESIZER : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //////////////
    //メンバー変数
    //////////////
    //2重起動防止用のMutex用ID名称
    CRESIZER(char* UName) : CDLG(UName) {}
    //GDI+用変数
    GdiplusStartupInput m_gdiSI;    //GDI+のスタートアップインプット
    ULONG_PTR m_gdiToken;            //GDI+のトークン
    HDC m_hDC;                        //メインダイアログのデバイスコンテキスト
    Image* m_pImage = 0;            //Imageクラスオブジェクトポインター

//(解説:Albumでも出てきたGDI+用の変数です。)

    //イメージファイル処理用変数
    int m_Max;                        //大きい方の一辺の値
    CSTR m_SrcPath;                    //ソースファイルパス
    CSTR m_DstPath;                    //ディスティネーションファイルパス
    int m_NoOfFiles = 0;            //イメージファイル処理数
    int m_Noc = 0;                    //縮小コピー処理数

//(解説:これはResizerに固有の処理用変数です。)

    //リストビューコントロール
    CLISTVIEW m_ListView;

//(解説:リストビューコントロールクラスをメンバー変数にしています。)

    //////////////
    //メンバー関数
    //////////////
    //メニュー項目、ダイアログコントロール関連
    bool OnSelsrc();
    bool OnOverwrite();
    bool OnSeldist();
    bool OnIdgo();
    bool OnIdok();
    bool OnIdcancel();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    //ユーザー定義関数
    bool SetFileNames(char*);
    bool IsDigitStr(char*);
    bool CopyImages(int, bool);
    int GetEncoderClsid(const WCHAR*, CLSID*);

//(解説:画像ファイルコピー用の関数です。)

};

////////////////////////////////////////////////////////////////////////
//派生させたCRESIZERクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////
CRESIZER Resizer("Resizer");    //ウィンドウクラスインスタンスの生成

BEGIN_MODALDLGMSG(ModalProc, Resizer)
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(Resizer, IDC_SELSRC, OnSelsrc())
    ON_COMMAND(Resizer, IDC_OVERWRITE, OnOverwrite())
    ON_COMMAND(Resizer, IDC_SELDIST, OnSeldist())
    ON_COMMAND(Resizer, IDC_GO, OnIdgo())
    ON_COMMAND(Resizer, IDOK, OnIdok())
    ON_COMMAND(Resizer, IDCANCEL, OnIdcancel())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
END_DLGMSG

////////////////////////
//コモンダイアログの作成
////////////////////////
CMNDLG g_Cmndlg;

//(解説:フォールダーを開くダイアログ用です。)


//////////////////////
//CEXTCHKのインスタンス
//////////////////////
CEXTCHK g_ExtChk;

//(解説:GDI+対象ファイルを開く際、および書き込みの際のエンコーダーを決定する際に使用します。)

 

次回はResizerProc.hで処理の定義を見てゆきましょう。

 

 

根がヒツコイ人間なのか、まだ「ファイルのプロパティダイアログの作成日時をコピーすると、年月日の数字の前後に'?'(ASCII 0x3F)が混入し、そのファイルを扱おうとするとエラーになる」Windowsのバグに引っかかっています。

 

 


こういう奴ですね。


実は「プログラムでエラーが出るファイルをどうしてOSは難なく処理できるのだろう?」という疑問を解くべくじたばたやっていました。その手伝いをしてくれたのが前に作ったCommand.exeです。

 

C:ドライブの下にDummyディレクトリーを作り、その中に「‎2022‎年‎4‎月‎11‎日のフォールダー」(プロパティダイアログ作成日時からコピーしたので'?'が数字の前後に入っていますが、amebaのエディターでは表示されていませんね)を作り、その中に「‎2022‎年‎4‎月‎11‎日のファイル.txt」(これにも'?'が数字の前後に入っています)を作ります。


これをDOS窓(昔のCommand.comを真似たCmd.exe)で確かめます。


ここでも'?'は表示されませんが、""で挟んだ場合、半角一文字分空白があることが分かるでしょうか?(なお、cd ""等のコマンドは手入力ではなく、表示された文字列を画面コピーしています。

これじゃぁ、しょうがない、ということで次はPowerShell(PS)で見てみます。

 

今度は明確に引数の'?'文字が表示されますが、プロンプトや表示される文字列では' '(半角空白文字)になってしまいます。

 

ということはよ、この禁則文字を空白文字で与えたら何とかなるのかしら?という浅はかな考えで実験(注)をします。

注:C++のrename関数も、Win32APIのMoveFile関数も受け付けないので開発を中断したFileNameCleanerと、GetLastError関数とFormatMessage関数を使ったErrorMsg関数(Error.hというヘッダーファイルに入れています)を使います。

 

実験結果は、

(1)オリジナルの'?'の入ったファイルパス名で発生するエラーの場合

→Error Codeは123(ERROR_INVALID_NAME)、「ファイル名、ディレクトリ名、またはボリューム ラベルの構文が間違っています。」で受け付けてくれません。

(2)オリジナルの'?'の入ったファイルパス名を’ ’(半角スペース)に置き換えた場合

→Error Codeは2(ERROR_FILE_NOT_FOUND)か3(ERROR_PATH_NOT_FOUND)、「指定されたファイルが見つかりません。」「指定されたパスが見つかりません。」でやはり討ち死にです。

 

では、何故Cmd.exeやPowerShell.exeは受け付けるのでしょうか?

 

(ひょっとしてこの謎を解いたら、また書きます。)

 

Albumは快調に稼働していますので、この中にDLLで含めたResizerの説明に移ります。

 

Resizerというのは、Album開発中に(写真によっては)それ程画像が大きく(画素数が多く)なくてよい画像があり、それを一定の条件で縮小するツールが欲しいなぁ、ということで開発しました。なお、Albumのツールには似たようなものがあるので、その違いと用途をヘルプファイルから引用します。Resizerは一括処理を行うので、個々の写真の回転指定は行えません。

【AlbumHelpから引用】

再確認すると、次の通りになります。
(i)  「表示」-「標準表示」-「サイズ変更画像の保存」メニューは、単一画像を
回転状態を含め、サイズを0 - 100%の間で変更の上、コピーまたは上書き保存します。
(ii) 「ツール」-「アルバム写真をフォールダーにまとめる」メニューは、それぞれの所在の如何を問わずアルバムファイル(*.alb)の全写真を指定(作成を含む)したフォールダーにコピーします。
(iii)
「ツール」-「Resizer」メニューは、1フォールダー内のGDI+対象写真をサイズ縮小条件を付けて他の場所へコピー、または上書き保存します。

 

処理の内容はAlbumの 「表示」-「標準表示」-「サイズ変更画像の保存」メニューに関わるGDI+処理をほぼ同じです。異なるのは「特定のフォールダーを選択し、コピー先(または「上書き保存」)を指定し、縦横いずれかの長い一辺の最大値を指定(注)してそれに応じて原寸の縦横比率で画像を縮小する」ことです。

注:指定した最大値よりも小さい画像は、上書き処理の場合は何もせず、それ以外の場合には縮小せずにコピーします。

 

ではResizer.rcを説明します。(ResResizer.hは単に100からの定数付与だけなので省略します。)

 

【Resizer.rc】

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------
#include    "ResResizer.h"

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 426, 254
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "画像サイズ変換"
FONT 9, "MS 明朝"
{
 CONTROL "選択", IDC_SELSRC, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 372, 4, 44, 14
 CONTROL "上書きする", IDC_OVERWRITE, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX | BS_CENTER | BS_VCENTER, 220, 38, 64, 10
 CONTROL "選択", IDC_SELDIST, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 372, 36, 44, 14
 CONTROL "", IDC_MAX, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 372, 68, 42, 14, WS_EX_CLIENTEDGE
 CONTROL "実行", IDC_GO, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 4, 218, 412, 14
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 372, 236, 44, 14
 CONTROL "ソースファイルパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 4, 4, 106, 10
 CONTROL "", IDC_SOURCE, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 4, 20, 416, 14, WS_EX_CLIENTEDGE
 CONTROL "ディスティネーションファイルパス", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 4, 38, 206, 10
 CONTROL "", IDC_DIST, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 4, 52, 416, 14, WS_EX_CLIENTEDGE
 CONTROL "画像の幅、高さの何れか大きい方の値設定(縦横比は原寸通り)", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 4, 72, 240, 10
 CONTROL "", IDC_LISTVIEW, "SYSLISTVIEW32", WS_CHILD | WS_VISIBLE | LVS_REPORT, 4, 86, 412, 132, WS_EX_CLIENTEDGE
}

//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "C:\Users\ysama\Programing\Borland C++\Resizer\Icon.ico"
 

↑の通り、内容はシンプルです。これは↓の様に表示されます。

 

 

ソースファイルパスの「選択」でディレクトリー(フォールダー)を選択すると、GDI+の対象ファイルがあればリストビューで表示されます。(この段階では「ファイル名」のみ。)

ディスティネーションファイルパスを「選択」するか、「上書き」にチェックを入れます。(「上書き」にチェックを入れるとソースファイルパスと同じ内容になります。)

その後、「画像の幅、高さiいずれか大きい方の値設定(縦横比は原寸通り)」に数値を入れます。大体640(小)、960、1,280(注)、1,920、2,560(大)という感じでしょうかね?(実際の画像は3,200x4,000とか2,400x3,200とかもっと大きいです。)

そして「実行」ボタンを押すとリストビューに幅と高さが表示され、縮小処理をされたもののみ「修正幅」と「修正高さ」が表示され、処理完了メッセージボックスが表示されます。

(個別写真の回転は指定できないので、それはAlbumの「表示」-「標準表示」-「サイズ変更画像の保存」メニューで個別に保存してください。)

 

Albumはバグ修正後好調で、料理の後、昔の旅行写真の整理やWin 10の背景スクリーンロールの整形に役立っています。

 

実は(私はPC時代の生まれなので、頑固にデスクトップ派なのですが)女房のASUSラップトップの「Windows セキュリティ」の警告Exclamationマークが消えない問題で昨日色々と調べ、対処し、ディスクスペースが小さいからじゃないかと思いましたが、結局「分からない」ので投げ出しました。

 

原因は特定できませんでしたが、「まぁ、なんかの為にはなるだろう」と女房の使わないようなWindows付帯のツールを軒並みアンインストールし、ディスクスペースを空け、その代わりにと私の「DirectShow」と「Album」を外部記憶装置(SDカードドライブ)に入れてあげました。試しに起動するとアプリのインストール制限ソフトが起動し、管理者権限でそれを外すと、

(1)DirectShowはやはり「フィルターが見当たりません」エラーとなります。女房のラップトップは記憶容量がもう少ないし、動画はWEBまたはiTuneがあるのでffdshowなどは入れずに削除しました。

(2)Albumは何ら問題なく作動しました。こいつは汎用のGDI+ベースで、単に表示するだけではなく、回転やサイズ変更ができるので残しました。

 

そんなことをしたら、あらー、「Windows セキュリティ」の警告Exclamationマークが消えています。こんなこともあるんですね。

 

なお、次のテーマが定まっていないので、Albumで解説し残したResizerの解説を次にやろうかと思います。

 

昨日上げたばかりなのに、早速のバグ修正で申し訳ありません。

 

(矢張り夜中に)突然「写真の削除で、選択写真番号の調整をしてなかったかも?」と思いついて、当時のコードを確認。

 

【当時のコード】

//フォトの削除
bool CALBUM::Delete(int n) {

    if(n >= m_NoI || n < 0)    //違法な引数
        return FALSE;
    m_Photo[n]->Delete();
    for(int i = n; n < m_NoI - 1; i++) {
        m_Photo[i] = m_Photo[i + 1];
    }
    if(m_NoI > 0)
        m_NoI--;
    m_Photo[m_NoI] = 0;
    return TRUE;
}

矢張り選択写真番号(m_Selected)を修正していません。仮にm_Selectedが最終写真(m_NoI - 1)であった場合、削除によりm_NoIがデクリメントしますので、m_Selectedは未生成CPHOTOポインター(m_Photo[m_NoI])を選択することになります。この場合、ShowImg関数の

 

    //エラー対応
    if(!m_Photo[m_Selected])
        return FALSE;
 

で落ちることはないと思いますが、何も反応しない状態を作り出すことになります。ということで、n番目を削除したら(nが最後の場合も想定し)一つ前のn-1番目を選択する仕様にしました。

 

ところが、動作試験でプログラムが落ちます!!!

 

なんで、どうして、????何しろ、GDI+のメソッドも使っていないし、演算もしていないのでDiv. by zeroなども発生し得ないからです。

これは(私の思い込みから)メッセージトラップによるエラーヶ所の検出までに大分時間がかかりました。(↑のコードのどこがバグだかわかりますか?)そして、

 

あ"~!!!!

 

    for(int i = n; n < m_NoI - 1; i++) {
 

大馬鹿じゃんっ!!!

ということが判明しました。(因みに条件式の"n < m_NoI -1"は「永遠のTRUE」となるので、i++されたメモリーアクセスは永遠に続き、違法アクセスとなります。)

 

ということでコードを以下のように修正し、ブログも修正箇所を赤字で修正しました。

//フォトの削除
bool CALBUM::Delete(int n) {

    if(n >= m_NoI || n < 0)    //違法な引数
        return FALSE;
    m_Photo[n]->Delete();
//誤    for(int i = n; 
n < m_NoI - 1; i++) {

    for(int i = n; i < m_NoI - 1; i++) {
        m_Photo[i] = m_Photo[i + 1];
    }
    if(m_NoI > 0) 
{  //(注)
        m_NoI--;
        m_Photo[m_NoI] = 0;

    }
    if(m_Selected && m_Selected >= n)
        --m_Selected;

    return TRUE;
}

注:この{}はなくとも効果は同じですが、m_Photo[m_NoI] = 0; の処理が一回分減ります。

 

大変お騒がせしました。修正後の動作は意図通りとなりました。

 

【Album】の(1)でVersion 1.0が完成、と書きましたが、その後ブログを書きながら実際に使ってみて動作試験を行い、使い勝手を改善する為の改良などを行い、本日に至りました。

 

mp4やmovファイルが最初読み込めなかったDirectShowと違い、GDI+は比較的簡単に必要な機能を特定して学習出来ましたが、数十のオーバーロード関数から必要なものを選んだり、表示の仕方をどうするか悩んだりしました。その中でも一番ショッキングだったのは矢張り「画像を読み込むと原因不明で落ちる現象(注)」で、プログラミング上の一番の悩みはスマホ画像が数十に増えると動作が重くなることでしたね。

注:「【バグは続くよ、何処までも】アルバムソフト-バグとの闘い」「【バグは続くよ、何処までも】その後...一応のピリオッド」参照

 

しかし、「紙ファイルのアルバムをめくり、写真をじっくり見る感覚」を基本仕様として、

(1)GDI+の対象とする静止画像を写真に見立てて、紙のアルバムを再現する

(2)処理スピードの遅さは画像ファイルのサイズ調整で対処する

という所期の目的と成果は何とか達成できたんじゃないかな、と思います。

 

実際に自分の料理や旅行の「写真」でAlbumを使いましたが、その際の所感を以下に述べます。

 

(1)Albumは「デジタルデータを迅速に、効率よくササっと処理する」のとは対極のソフトです。「(面倒だから避けていたのですが)決心して、PC内を整理し始め、自動作成されてよく分からないファイル名の画像ファイルを開いて『あー、あの時の写真だったのかぁ』と思わず整理の手を止めて眺めてしまい、ファイル名を(プロパティの撮影日時を確認して)自分の言葉で付け直して、ドラッグアンドドロップで放り込む」ような、アナログでレトロなソフトですね。(できればDirectShowで好きな昭和の楽曲をプレイリストで掛けて、コーヒーなんざ飲みながら、というのがもっと良いですが。)

 

(2)そういう意味でAlbum自体には「画像ファイルをドラッグアンドドロップ」し、写真の順番などの編集はFileListの並べ替え機能で行うのは(紙ファイルのアルバムでは並べ替えなんて大変な作業ですからまずしませんし)正しい選択だったんだろうと思います。

 

(3)表示を「サムネイル表示」と「標準表示」の2モードにし、サムネイル表示は「余り色々なことができずに単にページをめくるだけ(「次へ」ボタン)」のアルバム感を出し、単一写真の標準表示で「いつの写真だったろうと確かめたり(写真の詳細情報-右クリックポップアップメニュー以下同じ)」、「裏に当時の思い出をメモ書きしたり(写真の裏書編集)」、「横になっていた写真を縦に直して(回転表示)」、「アルバムに入れなおしたり(サイズ変更画像の保存)」することで、紙ファイルのアルバムを扱っている感じが出たんじゃないでしょうか?

 

(4)「サムネイル表示」の処理の重さはファイルサイズの変更で解消できますが、どこまで縮小するかの処理は扱っている写真によるんじゃないでしょうか?ということで標準表示の際に単一が同ファイルを「横になっていた写真を縦に直して(回転表示)アルバムに入れなおす(サイズ変更画像の保存)」ことでまず画像ファイル原本の整理をし、全体のサイズをそろえるには「ツール」の「アルバム写真をフォールダーにまとめる」で保存場所が様々なファイルを一つのフォールダー内にコピーし、「ツール」の「Resizer」をつかって「(例えば)長い方の一辺を960ピクセルにする」などとして3,200 x 4,000とか3,200 x 2,400とかの大きいファイルを縮小すると取り扱いに便利です。(例として、親族や友人といった何度かの旅行のファイルでその人が写っている画像をメモリースティックかSDカードに纏めて渡すような場合を想像してください。)

 

(5)しっかし、今でも合点がいかないのは「プロパティダイアログの日付をコピーして貼り付けると、数字が'?'(ASCII 0x3E)で挟まれること」です。今回の動作試験でも、「ファイル名を(プロパティの撮影日時を確認して)自分の言葉で付け直して」でコピペすると矢張り「?yyyy?年?mm?月?dd?日」となります。「詳細」の"yyyy/mm/dd"のデータをコピーしようにも、これはMCコントロールのデータで文字としてコピーできません。どうにかならないものでしょうかね?

 

いずれにしても、そんなことで私は昔の旅行写真アルバムや料理写真アルバム(↓)に裏書を付けて楽しんでいます。

 

 

ps. そんなこんなことを言っていたら、昨日即落ちしました。理由はやはりサムネイルの表示で「サムネイルサイズがウィンドウクライアント領域よりも大きいと列、行計算でDiv. by zeroエラーが発生する」ことでした。そんなことで、公開済コードも一部赤字修正を入れました。ご確認ください。まぁ、こういうことは使ってみないと分からないねぇ。

【修正】

【Album】Albumcpp、Album.hとUser.h~(7)

【Album】CALBUMの概要~各論(定義部)~(5)

 

またまたブログエディターから時数超過です!、っと怒られましたので、ウィンドウの操作系であるメンバー変数のOnVersion関数までを「その1」とし、ユーザー定義関数とダイアログ関連を「その2」で解説します。(字数制限があるので大分手抜きですが。)

 

/////////////////////////////////////////////
//ユーザー定義関数
//*.albファイルを読む
//newone: TRUE-先頭、FALSE-m_Selectedの位置
/////////////////////////////////////////////
bool CMyWnd::ReadAlbum() {

    //現在登録された写真がある場合
    if(g_Album.GetCount()) {
        int res = MessageBox(m_hWnd, "現在イメージデータが残っています。\n"\
                        "削除しますか?(はい)、追加しますか(いいえ)、"\
                        "処理を中止しますか(キャンセル)",
                        "削除確認", MB_YESNOCANCEL | MB_ICONQUESTION);
        switch(res) {
        case IDYES:
            g_Album.DeleteAll();
            break;
        case IDNO:
            break;
        case IDCANCEL:
            return FALSE;
        }
    }
    //g_FileNameのファイルをg_Fileにも見込めたか?
    if(!g_File.FromFile(g_FileName.ToChar())) {    //albファイルを読み込めたか?
        MessageBox(m_hWnd, "albファイルを読み込めませんでした", "エラー",
                    MB_OK | MB_ICONERROR);
        g_FileName = "";                        //ファイル名データを初期化
        return FALSE;
    }
    //アルバムに写真データ読込
    CSTR fn, cmt;
    while(g_File.Next(fn)) {    //ファイル名の切り出し
        g_File.Next(cmt);        //コメントの切り出し
        if(!g_Album.Add(fn.ToChar(), "", cmt.ToChar())) {    //写真の追加
            MessageBox(m_hWnd, "写真を追加できませんでした", "エラー", MB_OK | MB_ICONERROR);
            g_Album.DeleteAll();//途中まで読み込んだファイルがあれば削除する
            g_Album.SetCol(255, 255, 255, 255);                //Alph = 255、白色で
            g_Album.Cls();                                    //画面クリアー
            g_File = "";        //一旦読み込んだファイルを廃棄
            g_FileName = "";    //ファイル名データを初期化
            return FALSE;
        }
    }
    return TRUE;
}
//(解説:アルバムファイルを読み、CPHOTOクラス配列に落とす処理です。既にあるデータに追加もできるようにしています。)

bool CMyWnd::ShowAlbum(bool newone) {

    //読み込んだアルバムファイル名をステータスバーに表示
    SBar.SetText(2, g_FileName.ToChar());
    //メニュー、ボタンを初期状態から変える
    ChangeState(TRUE);
    //メニュー、ツールバーボタンの初期状態
    ChangeMode(g_ShowMode);
    //新しいアルバム(newone == TRUE)なら先頭の写真を選択
    if(newone)
        g_Album.Select(0);
    //スライダーを写真の先頭にセットする
    g_Slider.SetRange(0, g_Album.GetCount() - 1);    //スライダーの範囲を0~イメージ数 - 1に設定
    g_Slider.SetPosition(g_Album.GetSelected());    //現在選択されている写真を選択
    //Alph = 255、白色で画面クリアー
    g_Album.SetCol(255, 255, 255, 255);
    g_Album.Cls();
    //写真の表示
    if(g_ShowMode) {    //単一表示の場合
        ShowWindow(g_Slider.GetHandle(), SW_SHOW);    //スライダーを表示
        g_Album.ShowImg(FALSE, FALSE);                //写真を画面中央にサイズを調整して表示
    }
    else {                //サムネイル表示の場合
        ShowWindow(g_Slider.GetHandle(), SW_HIDE);    //スライダーを非表示
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        g_Album.ShowThumbnail(g_ThumbSize);
    }
    return TRUE;
}
//(解説:モード別に写真画像を表示する処理です。内容は既に説明している通りです。)

///////////////////////////////////
//ユーザー定義関数
//保存メニュー切替
//flag: FALSE-写真がなくなった場合
//flag: TRUE-データがある場合
//編集メニュー切替
//flag: FLASE-写真がなくなった場合
//flag: TRUE -写真がある場合
///////////////////////////////////
void CMyWnd::ChangeState(bool flag) {

    //ファイルメニューハンドルの取得
    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 0);
    if(flag) {
        //アルバムの保存メニュー
        EnableMenuItem(hMenu, IDM_SAVE, MF_BYCOMMAND | MF_ENABLED);
        EnableMenuItem(hMenu, IDM_SAVEAS, MF_BYCOMMAND | MF_ENABLED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_SAVE, MAKELONG(TBSTATE_ENABLED, 0));
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_SAVEAS, MAKELONG(TBSTATE_ENABLED, 0));
    }
    else { 
        //アルバムの保存メニュー
        EnableMenuItem(hMenu, IDM_SAVE, MF_BYCOMMAND | MF_GRAYED);
        EnableMenuItem(hMenu, IDM_SAVEAS, MF_BYCOMMAND | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_SAVE, MAKELONG(0, 0));
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_SAVEAS, MAKELONG(0, 0));
    }
    //編集メニューハンドルの取得
    hMenu = GetSubMenu(GetMenu(m_hWnd), 1);
    if(flag) {
        //削除メニュー
        EnableMenuItem(hMenu, IDM_DEL, MF_BYCOMMAND | MF_ENABLED);
        //削除ボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_DEL, MAKELONG(TBSTATE_ENABLED, 0));
    }
    else { 
        //削除メニュー
        EnableMenuItem(hMenu, IDM_DEL, MF_BYCOMMAND | MF_GRAYED);
        //削除ボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_DEL, MAKELONG(0, 0));
    }
    //表示メニューハンドルの取得
    hMenu = GetSubMenu(GetMenu(m_hWnd), 2);
    //前へメニュー
    EnableMenuItem(hMenu, IDM_PREV, MF_BYCOMMAND | (flag ? MF_ENABLED : MF_GRAYED));
    //次へメニュー
    EnableMenuItem(hMenu, IDM_NEXT, MF_BYCOMMAND | (flag ? MF_ENABLED : MF_GRAYED));
    if(flag) {
        //前へボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_PREV, MAKELONG(TBSTATE_ENABLED, 0));
        //次へボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_NEXT, MAKELONG(TBSTATE_ENABLED, 0));
    }
    else { 
        //前へボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_PREV, MAKELONG(0, 0));
        //次へボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_NEXT, MAKELONG(0, 0));
    }
}
//(解説:データが無い、またはある場合のメニュー、ツールバーボタンの状態を変えます。)

///////////////////////////////////
//ユーザー定義関数
//表示メニュー切替
//flag: TRUE-標準表示
//FALSE-サムネイル表示
///////////////////////////////////
void CMyWnd::ChangeMode(bool flag) {

    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 2);    //表示メニューハンドルの取得
    //メニューバー
    EnableMenuItem(hMenu, 0, MF_BYPOSITION | (flag ? MF_GRAYED : MF_ENABLED));
    EnableMenuItem(hMenu, 1, MF_BYPOSITION | (flag ? MF_ENABLED : MF_GRAYED));
    //メニューアイテム
    MENUITEMINFO mii;
    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_STATE;
    if(flag) {
        //メニュー
        mii.fState = MFS_CHECKED;
        SetMenuItemInfo(hMenu, 1, TRUE, &mii);
        EnableMenuItem(hMenu, IDM_LARGE, MF_BYCOMMAND | MF_GRAYED);
        EnableMenuItem(hMenu, IDM_SMALL, MF_BYCOMMAND | MF_GRAYED);
        EnableMenuItem(hMenu, IDM_ORIG, MF_BYCOMMAND | MF_ENABLED);
        EnableMenuItem(hMenu, IDM_ADJT, MF_BYCOMMAND | MF_ENABLED);
        EnableMenuItem(hMenu, IDM_EDIT, MF_BYCOMMAND | MF_ENABLED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_NORM, MAKELONG(TBSTATE_PRESSED, 0));
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_EDIT, MAKELONG(TBSTATE_ENABLED, 0));
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_THUMNAIL, MAKELONG(TBSTATE_ENABLED, 0));
    }
    else { 
        //メニュー
        mii.fState = MFS_CHECKED;
        SetMenuItemInfo(hMenu, 0, TRUE, &mii);
        EnableMenuItem(hMenu, IDM_LARGE, MF_BYCOMMAND | MF_ENABLED);
        EnableMenuItem(hMenu, IDM_SMALL, MF_BYCOMMAND | MF_ENABLED);
        EnableMenuItem(hMenu, IDM_ORIG, MF_BYCOMMAND | MF_GRAYED);
        EnableMenuItem(hMenu, IDM_ADJT, MF_BYCOMMAND | MF_GRAYED);
        EnableMenuItem(hMenu, IDM_EDIT, MF_BYCOMMAND | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_NORM, MAKELONG(TBSTATE_ENABLED, 0));
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_EDIT, MAKELONG(0, 0));
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_THUMNAIL, MAKELONG(TBSTATE_PRESSED, 0));
    }
}
//(解説:表示モードを変えた場合にメニュー、ツールバーの状態を変化させます。)

///////////////////////////////////
//ユーザー定義関数
//回転、反転メニュー切替
///////////////////////////////////
void CMyWnd::ChangeRotateFlip(int n) {

    //既にチェックが入っているメニューアイテム
    static int old = 0;
    //表示メニューハンドルの取得
    HMENU hMenu = GetSubMenu(GetSubMenu(GetSubMenu(GetMenu(m_hWnd), 2), 1), 2);
    //メニューアイテム
    MENUITEMINFO mii;
    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_STATE;
    mii.fState = MFS_ENABLED;
    //既に入っているチェックを外す
    SetMenuItemInfo(hMenu, old, TRUE, &mii);
    //新しくチェックを入れる
    mii.fState = MFS_CHECKED;
    SetMenuItemInfo(hMenu, n, TRUE, &mii);
    //回転、反転状況をステータスバーに表示
    SBar.SetText(1, g_RotateFlip[n]);
    old = n;
}
//(解説:回転表示のメニューアイテムのチェックを変化させます。)

///////////////////////////////
//RESIZEダイアログの関数定義
//コントロール関数
///////////////////////////////
bool RESIZEDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //作業用文字列変数
    char buff[MAX_PATH];
    //エラーチェック
    if(!g_Album.GetCount) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        EndModal(FALSE);
        return TRUE;
    }
    //現在選択されている写真のファイル名の表示
    SendItemMsg(IDC_SOURCE, WM_SETTEXT, 0, (LPARAM)g_Album.GetFileName());
    //画像形式をチェックして表示
    int n = g_ExtChk.CheckExt(g_Album.GetFileName(), IMGFILTER);
    if(!n) {
        MessageBox(m_hWnd, "対象ファイルではありません", "エラー", MB_OK | MB_ICONERROR);
        EndModal(FALSE);
        return TRUE;
    }
    else if(n > 2)
        n -= 1;
    lstrcpyW(m_Encoder, g_EncoderList[n - 1]);    //メンバー変数m_Encoderにエンコーダー名を保存
    wcstombs(buff, g_EncoderList[n - 1], 12);
    SendItemMsg(IDC_IMGTYPE, WM_SETTEXT, 0, (LPARAM)buff);
    //写真のファイル名からイメージインスタンスを生成
    mbstowcs(m_WName, g_Album.GetFileName(), MAX_PATH);
    m_pImage = new Image(m_WName);
    if(!m_pImage) {
        MessageBox(m_hWnd, "写真を読み込めませんでした", "エラー", MB_OK | MB_ICONERROR);
        EndModal(FALSE);
        return TRUE;
    }
    //読み込めたら現在の回転を適用する
    RotateFlipType rft = g_Album.GetRotateFlip();
    m_pImage->RotateFlip(rft);
    n = 0;
    switch(rft) {
    case RotateNoneFlipNone:
        n = 0;
        break;
    case Rotate90FlipNone:
        n = 1;
        break;
    case Rotate180FlipNone:
        n = 2;
        break;
    case Rotate270FlipNone:
        n = 3;
        break;
    }
    SendItemMsg(IDC_ROTATE, WM_SETTEXT, 0, (LPARAM)g_RotateFlip[n]);
    //幅と高さを取得してエディットボックスに代入
    m_ImgW = m_pImage->GetWidth();
    m_ImgH = m_pImage->GetHeight();
    wsprintf(buff, "%d", m_ImgW);
    SendItemMsg(IDC_WIDTH, WM_SETTEXT, 0, (LPARAM)buff);
    wsprintf(buff, "%d", m_ImgH);
    SendItemMsg(IDC_HEIGHT, WM_SETTEXT, 0, (LPARAM)buff);
    //スライダー初期設定-ハンドルの取得
    m_hSlider = GetDlgItem(m_hWnd, IDC_TRACKBAR);
    //20の目盛りを設定
    SendMessage(m_hSlider, TBM_SETTICFREQ, 20, 0);
    //0-100の範囲を設定
    SendMessage(m_hSlider, TBM_SETRANGE, TRUE, (LPARAM)MAKELONG(0, 100));
    //マウスクリックの際の移動量
    SendMessage(m_hSlider, TBM_SETPAGESIZE, 0, (LPARAM)10);
    //矢印キーの際の移動量
    SendMessage(m_hSlider, TBM_SETLINESIZE, 0, (LPARAM)1);
    //スライダーの位置を設定
    SendMessage(m_hSlider, TBM_SETPOS, TRUE, (LPARAM)100);
    //調整幅、高さをエディットボックスに代入
    wsprintf(buff, "%d", m_ImgW);
    SendItemMsg(IDC_ADJW, WM_SETTEXT, 0, (LPARAM)buff);
    wsprintf(buff, "%d", m_ImgH);
    SendItemMsg(IDC_ADJH, WM_SETTEXT, 0, (LPARAM)buff);
//(解説:長々と書いていますが、これは全てダイアログの初期設定です。結構コントロールがあり、且つImageクラスオブジェクトの情報を取ってコントロールに反映させることから長くなってしまいました。内容はコメントの通りです。)
    return TRUE;
}

bool RESIZEDLG::OnHScroll(WPARAM wParam, LPARAM lParam) {

    //作業用文字列変数
    char buff[MAX_PATH];
    //スライダー位置
    int pos;
    if(lParam != (LPARAM)m_hSlider)
        return FALSE;                    //スライダーからの通知でなければ何もしない
    switch(LOWORD(wParam)) {            //スライダーコントロールからの通知
    case SB_THUMBTRACK:                    //スライダーのドラッグ時と
    case SB_THUMBPOSITION:                //摘みを離した時に
        pos = HIWORD(wParam);            //スライダーの位置を取得
        break;
    default:                            //それ以外の場合HIWORD(wParam)は使えない
        pos = SendMessage(m_hSlider, TBM_GETPOS, 0, 0);    //TBM_GETPOSで位置を取得
        break;
    }
    int w = m_ImgW * pos / 100;
    int h = m_ImgH * pos / 100;
    wsprintf(buff, "%d", w);
    SendItemMsg(IDC_ADJW, WM_SETTEXT, 0, (LPARAM)buff);
    wsprintf(buff, "%d", h);
    SendItemMsg(IDC_ADJH, WM_SETTEXT, 0, (LPARAM)buff);
//(解説:スライダーを動かすと、0 - 100の数値が返ってくるので、それをImageオブジェクトの幅、高さ原寸にかけ、100で割り、調整幅、高さを求めて表示します。)
    return TRUE;
}

bool RESIZEDLG::OnIdok() {

    //処理の意向確認
    int res = MessageBox(m_hWnd, "ファイルを上書きしますか(はい)、"\
                        "別名保存しますか(いいえ)、処理を中止しますか(キャンセル)?",
                        "確認", MB_YESNOCANCEL | MB_ICONQUESTION);
    if(res == IDCANCEL) {
        //m_pImageのメモリーを解放
        delete m_pImage;
        EndModal(FALSE);
        return TRUE;
    }
    else if(res == IDNO) {
        //「ファイルを保存」ダイアログでパス、ファイル名を指定
        char* cp = g_Cmndlg.GetFileName(m_hWnd, "全てのファイル\0*.*\0\0", FALSE);
        if(!cp) {
            MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }
            lstrcpy(m_NewName, cp);
    }
    else {    //IDYES
        //上書き用ファイル名("(Originalファイル名).bak")を設定
        lstrcpy(m_NewName, g_Album.GetFileName());
        lstrcpy(strstr(m_NewName, "."), ".bak");
    }
    //作業用文字列変数
    char buff[MAX_PATH];
    //縮小修正した幅、高さを取得
    SendItemMsg(IDC_ADJW, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
    int w = atoi(buff);
    SendItemMsg(IDC_ADJH, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
    int h = atoi(buff);
    //縮小画像用のBitmapオブジェクトをオリジナルの縦横比で生成
    Bitmap* pBitmap = new Bitmap(w, h);
    //Bitmapオブジェクトから出力用のGraphicsフレームオブジェクトを作成
    Graphics* pFrame = Graphics::FromImage(pBitmap);
    //Rectオブジェクトのフレームの矩形を生成
    Gdiplus::Rect rtFrame(0, 0, w, h);
    //画像の全体をフレームのrtFrameで表した矩形領域へ描画
    pFrame->DrawImage (m_pImage, rtFrame, 0, 0, m_ImgW, m_ImgH, UnitPixel);    //1ポイント == 1/72インチ
    // エンコーダのCLSIDを取得
    CLSID encoderClsid;
    if(GetEncoderClsid(m_Encoder, &encoderClsid) == -1) {    //m_Encoderに保存してあるエンコーダー型
        MessageBoxW(m_hWnd, m_WName, L"エンコーダーCLSIDの取得に失敗しました", MB_OK | MB_ICONERROR);
        //ビットマップのメモリーを解放
        delete pBitmap;
        //m_pImageのメモリーを解放
        delete m_pImage;
        EndModal(FALSE);
        return TRUE;
    }
    //EncoderParameters配列の第1オブジェクトを使って品質を指定
    LONG lQuality = 100;    //品質レベルは100の最高品質
    //EncoderParameters配列オブジェクト
    EncoderParameters EncoderParams;
    //先頭のEncoderParameterオブジェクト
    EncoderParams.Parameter[0].Guid = EncoderQuality;                    //品質であることを指定
    EncoderParams.Parameter[0].NumberOfValues = 1;                        //Valueの変数数は1つ
    EncoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;    //Valueの変数の長さ
    EncoderParams.Parameter[0].Value = (VOID*) &lQuality;                //品質指定
    EncoderParams.Count = 1;                                            //EncoderParameters配列は一つだけ
    //フレームとして使ったBitmapを指定エンコーダー、品質でファイルに保存
    mbstowcs(m_WName, m_NewName, MAX_PATH);
    pBitmap->Save(m_WName, &encoderClsid, &EncoderParams);
    //ビットマップのメモリーを解放
    delete pBitmap;
    //m_pImageのメモリーを解放
    delete m_pImage;
    //上書き処理
    if(res == IDYES) {
        DeleteFile(g_Album.GetFileName());            //オリジナルファイルを削除
        MoveFile(m_NewName, g_Album.GetFileName());    //縮小ファイルをオリジナル名に変更
    }
    EndModal(TRUE);
//(解説:可也細かくコメントを書いていますが、要すればダイアログで表示されている回転状態、調整幅、高さのImageオブジェクトをビットマップフレームに書き出し、選択されたエンコーダーでその型式のファイルで書きだします。「別名保存」の場合はそのファイル名そのままで、「上書き保存」の場合は(ImageのSaveメソッドが上書きを禁止しているので)一旦*.bakで書き出し、オリジナルを削除してからファイル名をオリジナルに変更します。)
    return TRUE;
}

bool RESIZEDLG::OnIdcancel() {

    //m_pImageのメモリーを解放
    delete m_pImage;
    EndModal(FALSE);
    return TRUE;
}
//(解説:Idokでも、Idcancelでも、newで生成したオブジェクト(メモリー割り付け)は必ずdelete処理するように気を付けてください。忘れるとメモリーリークが発生します。)

////////////////////////////////////////////////////
//ユーザー定義関数
//Image CODEC CLSIDの取得(出典:Microsoft Doc)
//formatに合致するCODECのCLSIDアドレスをpClsidに渡す
//戻り値:成功-Image CODEC配列(ImageCodecInfo)引数
//    失敗- -1
////////////////////////////////////////////////////
int RESIZEDLG::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {

    //イメージCODECの数、サイズ取得
    UINT  num = 0;        //image encoderの数
    UINT  size = 0;        //image encoder配列のサイズ(バイト数)
    GetImageEncodersSize(&num, &size);
    if(size == 0) {
        return -1; //イメージCODECサイズ取得失敗
    }
    //イメージCODEC情報用クラス(ImageCodecInfo)
    ImageCodecInfo* pImageCodecInfo = NULL;
    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo == NULL) {
        return -1; //メモリ確保失敗
    }
    //イメージCODEC情報クラス取得
    GetImageEncoders(num, size, pImageCodecInfo);
    for(UINT j = 0; j < num; ++j) {
        //formatがCODECのMIME(Multipurpose Internet Mail Extensions)型なら
        if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
            //J番目のCODECのグローバル一意識別子(CLSID)を渡す
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;    //成功
        }  
    }
    free(pImageCodecInfo);
    return -1;            // 失敗 - 要求した種類のエンコーダが存在しなかった
}
//(解説:表題に書かれている通り、Microsoft Docのコードをベースにちょっと書き換えました。コメントも日本語にしているのでわかりやすいと思います。)

///////////////////////////////
//EDITダイアログの関数定義
//コントロール関数
///////////////////////////////
bool EDITDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //クライアントエリアサイズを記録
    RECT rec;
    GetClientRect(m_hWnd, &rec);
    m_Width = rec.right - rec.left;
    m_Height = rec.bottom - rec.top;
    //IDC_EDITコントロールを展開する(初期状態で0x7FFF-32,767字迄取り扱い可能)
    MoveWindow(GetDlgItem(m_hWnd, IDC_EDIT), 2, 2, m_Width - 4, m_Height - 36, TRUE);
    //コメントを読み込む
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)g_Album.GetComment());
    //IMEの変換状態を全角ひらがなにセットする
    m_hImc = ImmGetContext(GetDlgItem(m_hWnd, IDC_EDIT));
    ImmSetOpenStatus(m_hImc, TRUE);
    if(!ImmSetConversionStatus(m_hImc, IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE, IME_SMODE_PHRASEPREDICT))
        MessageBox(m_hWnd, "全角ひらがな連文節変換入力にセットできませんでした", "エラー", MB_OK | MB_ICONERROR);
    return TRUE;
}
//(解説:↑の「IMEの変換状態を全角ひらがなにセットする」のところがやや目新しいところです。

 

 

bool EDITDLG::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_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 + 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;
}

bool EDITDLG::OnMinMax(WPARAM wParam, LPARAM lParam) {

    //典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = 320;    //クライアントエリア + 16
    pmmi->ptMinTrackSize.y = 240;    //クライアントエリア + 61
    return FALSE;                    //処理はDefWndProcに任す
}

bool EDITDLG::OnIdok() {

    //IMEを閉じる
    ImmSetOpenStatus(m_hImc, FALSE);
    ImmReleaseContext(GetDlgItem(m_hWnd, IDC_EDIT), m_hImc);
    //IDC_EDITコントロールの文字列を取得する(初期状態で0x7FFF-32,767字迄取り扱い可能)
    char buff[MAXCMT];
    //コメントを読み込む
    SendItemMsg(IDC_EDIT, WM_GETTEXT, MAXCMT, (LPARAM)buff);
    g_Album.SetComment(buff);
    EndModal(TRUE);
    return TRUE;
}

bool EDITDLG::OnIdcancel() {

    EndModal(FALSE);
    return TRUE;
}
//(解説:文字列の編集を行い、CPHOTOのm_Commentを更新する単なるエディットコントロール一つのダイアログですが、サイズを可変にしてコントロールサイズも変更し、最小サイズを制限しているのでコードが長くなっています。)

///////////////////////////////
//VERSIONダイアログの関数定義
//コントロール関数
///////////////////////////////
bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
}

ゼイ、ゼイ、という感じですが、やっと終わりにたどり着きました。お疲れさまでした。
次回はブログベースで最終でコンパイルとアルバムを実際に使用した雑感などを書いてみましょう。

 

最後のファイルです。ポイントを解説します。(なお、以下の処理で頻繁にif(g_Album.GetCount())という条件節が出てきます。これはオブジェクト生成前にGDI+メソッドを行わせようとするとプログラムが即落ちするので、その防止策です。)

【AlbumProc.h】
//////////////////////////////////////////
// AlbumProc.h
// Copyright (c) 02/28/2022 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnCreate(WPARAM wParam, LPARAM lParam) {

    //コモンコントロールの初期化
    InitCommonControls();

    //ツールバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    TBar.Init(m_hWnd, m_hInstance, TOOLBAR);
    //ツールバーボタン用カスタムビットマップ追加
    TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 13);
    //ツールバーボタン追加
    TBBUTTON tbb[18];
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = TBar.m_id + 0;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_NEW;
    tbb[1].iBitmap = TBar.m_id + 1;
    tbb[1].fsState = TBSTATE_ENABLED;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = IDM_OPEN;
    tbb[2].iBitmap = TBar.m_id + 2;
    tbb[2].fsStyle = TBSTYLE_BUTTON;
    tbb[2].idCommand = IDM_SAVE;
    tbb[3].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[4].iBitmap = TBar.m_id + 3;
    tbb[4].fsState = TBSTATE_ENABLED;
    tbb[4].fsStyle = TBSTYLE_BUTTON;
    tbb[4].idCommand = IDM_EXIT;
    tbb[5].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[6].iBitmap = TBar.m_id + 4;
    tbb[6].fsState = TBSTATE_ENABLED;
    tbb[6].fsStyle = TBSTYLE_BUTTON;
    tbb[6].idCommand = IDM_ADD;
    tbb[7].iBitmap = TBar.m_id + 5;
    tbb[7].fsStyle = TBSTYLE_BUTTON;
    tbb[7].idCommand = IDM_DEL;
    tbb[8].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[9].iBitmap = TBar.m_id + 6;
    tbb[9].fsState = TBSTATE_CHECKED;    //TBSTATE_ENABLED;
    tbb[9].fsStyle = TBSTYLE_BUTTON;
    tbb[9].idCommand = IDM_THUMNAIL;
    tbb[10].iBitmap = TBar.m_id + 7;
    tbb[10].fsStyle = TBSTYLE_BUTTON;
    tbb[10].idCommand = IDM_PREV;
    tbb[11].iBitmap = TBar.m_id + 8;
    tbb[11].fsStyle = TBSTYLE_BUTTON;
    tbb[11].idCommand = IDM_NEXT;
    tbb[12].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[13].iBitmap = TBar.m_id + 9;
    tbb[13].fsState = TBSTATE_ENABLED;
    tbb[13].fsStyle = TBSTYLE_BUTTON;
    tbb[13].idCommand = IDM_NORM;
    tbb[14].iBitmap = TBar.m_id + 10;
    tbb[14].fsStyle = TBSTYLE_BUTTON;
    tbb[14].idCommand = IDM_EDIT;
    tbb[15].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[16].iBitmap = TBar.m_id + 11;
    tbb[16].fsState = TBSTATE_ENABLED;
    tbb[16].fsStyle = TBSTYLE_BUTTON;
    tbb[16].idCommand = IDM_HELP;
    tbb[17].iBitmap = TBar.m_id + 12;
    tbb[17].fsState = TBSTATE_ENABLED;
    tbb[17].fsStyle = TBSTYLE_BUTTON;
    tbb[17].idCommand = IDM_VERSION;
    TBar.AddButtons(18, tbb);

    //ステータスバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    SBar.Init(m_hWnd, m_hInstance, STATUSBAR);
    //ステータスバー区画設定
    int sec[3] = {100, 200, -1};
    SBar.SetSection(3, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "Album Version 1.0");
    SBar.SetText(1, "回転表示状態");
    SBar.SetText(2, "アルバムファイル(*.alb)名");
//(解説:ほぼSkeltonWizard通りです。ステータスバー区画は、後で回転状態を示そうと思ったので一つ増やし、久々に3分割になりましたね。)

    //CALBUMインスタンスの初期化
    g_Album.Init(m_hWnd);
//(解説:これは出力先ウィンドウに一回だけ行います。)

    //再生用スライダーコントロールの作成
    g_Slider.Create(TBar.GetHandle(), IDC_SLIDER, 340, 0, 300, 24);
    ShowWindow(g_Slider.GetHandle(), SW_HIDE);    //最初はスライダーを隠しておく
    //スライダーの親であるツールバーのWM_HSCROLL処理を行う為のサブクラス化
    g_TBProc = (WNDPROC)GetWindowLongPtr(GetDlgItem(m_hWnd, TOOLBAR), GWLP_WNDPROC);
    SetWindowLongPtr(GetDlgItem(m_hWnd, TOOLBAR), GWLP_WNDPROC, (LONG_PTR)TOOLBARProc);
    g_Slider.SetRange(0, g_Album.m_NoI);    //スライダーの範囲を0~イメージ数に設定
    g_Slider.SetTic(1);                        //スライダーメモリ設定
//(解説:ここの処理はDirectShowの時と同じで、再利用しています。)

    //AlbumHelpへのパス設定
    g_Help = g_Arg.Path();
    g_Help = g_Help + "\\AlbumHelp.chm";
//(解説:CARGクラスのg_Argを使ってヘルプファイルを捕まえておきます。)

    //AlbumListへのパス設定とディレクトリーの作成
    g_Path = g_Arg.Path();
    g_Path = g_Path + "\\AlbumList";
    CreateDirectory(g_Path.ToChar(), NULL);
//(解説:対象となる写真画像はPC内に散在していても大丈夫ですが、アルバムファイルは散逸させないようにこのフォールダーだけにとどめたいと思いました。)

    //ドラッグアンドドロップの受付開始
    DragAcceptFiles(m_hWnd, TRUE);
//(解説:写真画像ファイル追加用です。)

    /////////////////////////////////////////////////////////////
    //OnCreateでファイル起動対応を行おうとすると、OnSize前なので
    //g_Album.m_Widthとm_Heightが未だ0(ゼロ)のままとなり、
    //ShowAlbum関数のところで"Division by zero"エラーとなる。
    //この為ファイル起動はg_ByFileフラグを立ててOnSizeに置く。
    //また、何を投入ファイルについては、
    //(1) *.albは専用フォールダー、パスを設けて集約しており、
    //(2) PCに散在するイメージファイルを対象にした。
    /////////////////////////////////////////////////////////////

//(解説:ファイル起動を行おうとすると"Division by zero"エラーで落ちることから苦肉の策です。)

    return TRUE;
}

bool CMyWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam) {

    if(!g_ShowMode) {    //サムネイル表示モードなら
        //マウスクリック時のx, y座標を取得する
        int x = GET_X_LPARAM(lParam);
        int y = GET_Y_LPARAM(lParam);
        //マウス座標から写真番号を算出して選択する
        g_Album.Select(g_Album.GetWhichThumb(g_ThumbSize, x, y));
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで現在の選択写真のあるページを表示
        g_Album.ShowThumbnail(g_ThumbSize);
    }
//(解説:サムネイルモードで写真選択処理(サーモンピンクの矩形枠を付ける)を行います。)
    return TRUE;
}

bool CMyWnd::OnRButtonDown(WPARAM wParam, LPARAM lParam) {

    if(g_Album.GetCount()) {
        HMENU hMenu;
        if(g_ShowMode) {    //単一表示の場合
            hMenu = GetSubMenu(GetSubMenu(GetMenu(m_hWnd), 2), 1);
        }
        else {                //サムネイル表示の場合
            hMenu = GetSubMenu(GetSubMenu(GetMenu(m_hWnd), 2), 0);
        }
        //ウインドウの位置情報を取得
        RECT rec;
        GetWindowRect(m_hWnd, &rec);
        //ポップアップメニューの表示
        POINT pt;
        GetCursorPos(&pt);
        TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON,
            pt.x, pt.y, 0, m_hWnd, &rec);
        return TRUE;
    }
    else
        return FALSE;
}
//(解説:右マウスボタンクリックはポップアップメニュー処理です。モード別になっています。)

bool CMyWnd::OnNotify(WPARAM wParam, LPARAM lParam) {

    //ツールバーツールチップ
    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {    //TTN_GETDISPINFOと同じ
        ((LPTOOLTIPTEXT)lParam)->hinst = m_hInstance;
        char* tag;
        switch(((LPTOOLTIPTEXT)lParam)->hdr.idFrom) {
        case IDM_NEW:
            tag = "アルバムの作成、編集";
            break;
        case IDM_OPEN:
            tag = "アルバムを開く";
            break;
        case IDM_SAVE:
            tag = "アルバムの保存";
            break;
        case IDM_EXIT:
            tag = "終了";
            break;
        case IDM_ADD:
            tag = "写真の追加";
            break;
        case IDM_DEL:
            tag = "写真の削除";
            break;
        case IDM_THUMNAIL:
            tag = "サムネイル表示";
            break;
        case IDM_PREV:
            tag = "前へ";
            break;
        case IDM_NEXT:
            tag = "次へ";
            break;
        case IDM_NORM:
            tag = "標準表示";
            break;
        case IDM_EDIT:
            tag = "写真の裏書編集";
            break;
        case IDM_HELP:
            tag = "Albumについて";
            break;
        case IDM_VERSION:
            tag = "バージョン情報";
            break;
        }
        ((LPTOOLTIPTEXT)lParam)->lpszText = tag;
        return TRUE;
    }
    return FALSE;
//(解説:定番のツールバーツールチップ処理です。)
}

bool CMyWnd::OnSize(WPARAM wParam, LPARAM lParam) {

    TBar.AutoSize();
    SBar.AutoSize();
    g_Slider.AutoSize(1, 0, 0);    //右上角に離さずに表示
    //メインウィンドウの有効クライアントエリアを取得
    g_Album.GetSize(lParam, TBar.GetHandle(), SBar.GetHandle());
//(解説:ここにGetSizeメソッドを置くことで常に新しいウィンドウサイズを監視できます。)
    //ファイル起動対応(g_ByFileが真の時、1回のみ)
    if(g_ByFile) {
        for(int i = 1; i < g_Arg.c(); i++) {
            if(g_ExtChk.CheckExt(g_Arg.v(i), IMGFILTER))    //対象ファイルか否かチェック
                g_Album.Add(g_Arg.v(i));                    //Image配列への追加
            else
                MessageBox(m_hWnd, g_Arg.v(i), "再生対象外のファイル", MB_OK | MB_ICONERROR);
        }
        //アルバムの表示(新しいアルバム)
        ShowAlbum(TRUE);
        g_ByFile = FALSE;
    }
//(解説:↑で述べた苦肉の策の中身です。)
    return TRUE;
}

bool CMyWnd::OnPaint(WPARAM wParam, LPARAM lParam) {

    PAINTSTRUCT paint;
    //仮想ウィンドウをウィンドウへ張り付ける
    g_Album.OnPaint(BeginPaint(m_hWnd, &paint));
//(解説:WWM_PAINTメッセージで処理する仮想ウィンドウ(ウィンドウとコンパチのビットマップ)を張り付ける処理です。)
    EndPaint(m_hWnd, &paint);
    return TRUE;
}

bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES)
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

bool CMyWnd::OnDropFiles(WPARAM wParam, LPARAM lParam) {

    //ドラッグアンドドロップされたファイル名取得と処理
    static char FileName[MAX_PATH];
    int n;                                    //ファイル数
    HDROP hDrop = (HDROP)wParam;             //HDROPを取得
    n = DragQueryFile(hDrop, -1, NULL, 0);    //ファイル数を取得
    for(int i = 0; i < n; i++) {
        DragQueryFile(hDrop, i, FileName, MAX_PATH);    //ファイル名を取得
        if(g_ExtChk.CheckExt(FileName, IMGFILTER)) {    //対象ファイルか否かチェック
            g_Album.Add(FileName);    //Image配列への追加
        }
        else
            MessageBox(m_hWnd, FileName, "再生対象外のファイル", MB_OK | MB_ICONERROR);
    }
    //ドラッグアンドドロップの終了
    DragFinish((HDROP)wParam);
    //アルバムの表示(写真の追加)
    ShowAlbum(FALSE);
    return TRUE;
}
//(解説:ドラッグアンドドロップの対象ファイルはフォールダーを定めた*.albではなく、散在する写真画像ファイルにしています。)

bool CMyWnd::OnMinMax(WPARAM wParam, LPARAM lParam) {

    //典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = MINW + 16;    //クライアントエリア + 16
    pmmi->ptMinTrackSize.y = MINH + 61;    //クライアントエリア + 61
    return FALSE;                            //処理はDefWndProcに任す
}

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CMyWnd::OnNew() {

    //ファイルリストダイアログで*.albファイルを作成、編集
    char* cp = GetFileList("アルバムファイル(*.alb)\0*.alb\0\0",
                            IMGFILTER, g_Path.ToChar(), "alb");
//(解説:DLLのFileListを呼び出しています。)
    //作成できたなら一旦保存してOnOpenで読み込む
    if(*cp) {
        g_FileName = cp;
        //アルバムファイルを読む
        ReadAlbum();
        //アルバムの表示(新しいアルバム)
        ShowAlbum(TRUE);
//(解説:「アルバムを読んで、表示する」という基本動作です。)
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnOpen() {

    //「ファイルを開く」ダイアログを使う
    char* cp = g_Cmndlg.GetFileName(m_hWnd, FILEFLT, TRUE, "alb", g_Path.ToChar());
    if(cp) {
        g_FileName = cp;    //ファイルを開くダイアログでファイルを選択
        //アルバムファイルを読む
        ReadAlbum();
        //アルバムの表示(新しいアルバム)
        ShowAlbum(TRUE);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
//(解説:FileListダイアログか、「ファイルを開く」ダイアログか、の違いだけですね。)
}

bool CMyWnd::OnSave() {

    //現在のファイル名が無ければ「名前を付けて保存」へ移行
    if(!*g_FileName.ToChar())
        return OnSaveas();
    //現在のファイル名で保存
    else {
        //現在の選択ファイルを保存
        int sel = g_Album.GetSelected();
        //g_Fileにファイルデータを入れ、保存する
        g_File = "";
        for(int i = 0; i < g_Album.GetCount(); i++) {
            g_Album.Select(i);
            g_File = g_File + "\"";
            g_File = g_File + g_Album.GetFileName();
            g_File = g_File + "\",\"";
            g_File = g_File + g_Album.GetComment();
            g_File = g_File + "\"\n";
        }
        g_File.ToFile(g_FileName.ToChar());
        //現在の選択ファイルに戻す
        g_Album.Select(sel);
//(解説:ちょうどsel変数がスタックの役割をしてくれています。)
    }
    return TRUE;
}

bool CMyWnd::OnSaveas() {

    //「ファイルを保存」ダイアログでパス、ファイル名を指定
    char* cp = g_Cmndlg.GetFileName(m_hWnd, FILEFLT, FALSE, "alb", g_Path.ToChar());
    if(cp)
        g_FileName = cp;    //ファイルを保存ダイアログでファイルを選択
    else {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //現在の選択ファイルを保存
    int sel = g_Album.GetSelected();
    //g_Fileにファイルデータを入れ、保存する
    g_File = "";
    for(int i = 0; i < g_Album.GetCount(); i++) {
        g_Album.Select(i);
        g_File = g_File + "\"";
        g_File = g_File + g_Album.GetFileName();
        g_File = g_File + "\",\"";
        g_File = g_File + g_Album.GetComment();
        g_File = g_File + "\"\n";
    }
    g_File.ToFile(g_FileName.ToChar());
    //現在の選択ファイルに戻す
    g_Album.Select(sel);
    //読み込んだアルバムファイル名をステータスバーに表示
    SBar.SetText(2, g_FileName.ToChar());
    return TRUE;
}
//(解説:ほぼSaveと同じなので、セーブ部分を下位関数にしてもよいですね。)

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
}

bool CMyWnd::OnAdd() {

    //「ファイルを開く」ダイアログで選択、追加
    if(!g_Album.Add("")) {
        MessageBox(m_hWnd, "写真を追加できませんでした", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //スライダーの範囲を0~イメージ数 - 1に設定
    g_Slider.SetRange(0, g_Album.GetCount() - 1);
    //スライダーの位置をイメージ数 - 1に設定
    g_Slider.SetPosition(g_Album.GetCount() - 1);
    //アルバムに追加された写真を選択
    g_Album.Select(g_Album.GetCount() - 1);
    //画面を消去
    g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
    g_Album.Cls();                        //画面クリアー
    if(g_ShowMode) {    //単一表示の場合
        //画面中央にサイズ調整して表示
        g_Album.ShowImg(FALSE, FALSE);
    }
    else {                //サムネイル表示の場合
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで現在の選択写真のあるページを表示
        g_Album.ShowThumbnail(g_ThumbSize);
    }
//(解説:この一連の動作は分かりやすいと思います。)
    //メニュー、ボタンを初期状態から変える
    ChangeState(TRUE);
    return TRUE;
}

bool CMyWnd::OnDel() {

    //最終確認
    if(MessageBox(m_hWnd, "選択された写真をアルバムから削除しますか?", "確認",
                    MB_YESNO | MB_ICONQUESTION) == IDNO) {
        return FALSE;
    }
    g_Album.Delete(g_Album.GetSelected());
    //スライダーの範囲を0~イメージ数 - 1に設定
    g_Slider.SetRange(0, g_Album.GetCount() - 1);
    g_Slider.SetPosition(g_Album.GetCount() - 1);
    //画面を消去
    g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
    g_Album.Cls();                        //画面クリアー
    if(g_ShowMode) {    //単一表示の場合
        //現在の選択写真を中央にサイズ調整して表示
        g_Album.ShowImg(FALSE, FALSE);
    }
    else {                //サムネイル表示の場合
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで現在の選択写真のあるページを表示
        g_Album.ShowThumbnail(g_ThumbSize);
    }
//(解説:ちょうど追加の反対の処理を行っているのが分かります。最初は必ずしも同じ書き方をしていませんでしたが、分かりやすくするために処理の順も併せました。)
    //写真がなくなったらメニュー、ボタンを初期状態にする
    if(!g_Album.GetCount())
        ChangeState(FALSE);
    return TRUE;
}

bool CMyWnd::OnThumnail() {

    //表示モードを変更
    g_ShowMode = FALSE;
    //メニューチェックを変更し、ツールバーボタンを表示
    ChangeMode(g_ShowMode);
    //スライダーを隠す
    ShowWindow(g_Slider.GetHandle(), SW_HIDE);
    if(g_Album.GetCount()) {
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで現在の選択写真のあるページを表示
        g_Album.ShowThumbnail(g_ThumbSize);
        return TRUE;
    }
//(解説:既に写真画像の追加、削除で見てきた処理です。)
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnPrevious() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //現在位置の取得
    int pos = g_Album.GetSelected();
    //最終位置の取得
    int max = g_Album.GetCount() - 1;
    //メニューとボタンの状態変更
    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 2);    //メニューハンドルの取得
    if(!pos) {                //先頭にいるなら「前へ」を無効にして戻る
        //メニュー
        EnableMenuItem(hMenu, IDM_PREV, MF_BYCOMMAND | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_PREV, MAKELONG(0, 0));
        //警告音
        MessageBeep(MB_ICONHAND);
        return TRUE;
    }
    else if(pos == max){    //末尾にいるなら「次へ」を有効にする
        //メニュー
        EnableMenuItem(hMenu, IDM_NEXT, MF_BYCOMMAND | MF_ENABLED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_NEXT, MAKELONG(TBSTATE_ENABLED, 0));
    }
//(解説:現在の位置と処理でメニューとボタンの状態を変化させる処理です。端にいて動かない時の警告音はMessageBeepを使いました。)
    //表示変更
    if(g_ShowMode) {    //単一表示の場合
        //写真を一つ戻す
        g_Album.Select(pos - 1);
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //画面中央サイズ調整で表示
        g_Album.ShowImg(FALSE, FALSE);
        //スライダーを一つ戻す
        g_Slider.SetPosition(g_Slider.GetPosition() - 1);
    }
    else {                //サムネイル表示の場合
        //選択写真を1ページ分戻す(1ページ分なければ先頭写真を選択)
        g_Album.Select(pos - g_Album.HowManyThumb(g_ThumbSize));
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで現在のページのひとつ前を表示
        g_Album.ShowThumbnail(g_ThumbSize);
    }
    return TRUE;
}

bool CMyWnd::OnNext() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //現在位置の取得
    int pos = g_Album.GetSelected();
    //最終位置の取得
    int max = g_Album.GetCount() - 1;
    //メニューとボタンの状態変更
    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 2);    //メニューハンドルの取得
    if(pos == max) {    //末尾にいるなら「次へ」を無効にして戻る
        //メニュー
        EnableMenuItem(hMenu, IDM_NEXT, MF_BYCOMMAND | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_NEXT, MAKELONG(0, 0));
        //警告音
        MessageBeep(MB_ICONHAND);
        return TRUE;
    }
    else if(!pos) {        //先頭にいるなら「前へ」を有効にする
        //メニュー
        EnableMenuItem(hMenu, IDM_PREV, MF_BYCOMMAND | MF_ENABLED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_SETSTATE, IDM_PREV, MAKELONG(TBSTATE_ENABLED, 0));
    }
    //表示変更
    if(g_ShowMode) {    //単一表示の場合
        //写真を一つ進める
        g_Album.Select(pos + 1);
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //画面中央サイズ調整で表示
        g_Album.ShowImg(FALSE, FALSE);
        //スライダーを一つ進める
        g_Slider.SetPosition(g_Slider.GetPosition() + 1);
    }
    else {                //サムネイル表示の場合
        //選択写真を1ページ分進める(1ページ分なければ最終写真を選択)
        g_Album.Select(pos + g_Album.HowManyThumb(g_ThumbSize));
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで現在の選択のページの次を表示
        g_Album.ShowThumbnail(g_ThumbSize);
    }
//(解説:既に説明している内容です。)
    return TRUE;
}

bool CMyWnd::OnLarge() {

    g_ThumbSize = LARGE;
    if(g_Album.GetCount()) {
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで選択写真を含むページを表示
        g_Album.ShowThumbnail(g_ThumbSize);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnSmall() {

    g_ThumbSize = SMALL;
    if(g_Album.GetCount()) {
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //サムネイル上の選択写真枠設定色とペン
        g_Album.SetCol(128, 255, 64, 64);
        g_Album.SetPen(8);
        //サムネイルで選択写真を含むページを表示
        g_Album.ShowThumbnail(g_ThumbSize);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}
//(解説:サムネイルの大、小変更処理です。内容は同じですね。サイズの違うMEDIUMを入れて同じ処理をすれば「中サイズ」処理ができます。)

bool CMyWnd::OnNorm() {

    //表示モードを変更
    g_ShowMode = TRUE;
    //メニューチェックを変更し、ツールバーボタンを非表示
    ChangeMode(g_ShowMode);
    //スライダーを現在選択されている写真で表示する
    ShowWindow(g_Slider.GetHandle(), SW_SHOW);
    g_Slider.SetPosition(g_Album.GetSelected());
    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    else {
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //選択写真を画面中央サイズ調整で表示
        g_Album.ShowImg(FALSE, FALSE);
        return TRUE;
    }
}
//(解説:単一画像表示処理です。画像の回転やサイズ変更とその変更の保存はこのモードで行うようにしています。)

bool CMyWnd::OnOrigin() {

    if(g_Album.GetCount()) {
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //選択写真を左上に原寸で表示
        g_Album.ShowImg(TRUE, TRUE);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnAdjust() {

    if(g_Album.GetCount()) {
        //画面を消去
        g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
        g_Album.Cls();                        //画面クリアー
        //選択写真を画面中央サイズ調整で表示
        g_Album.ShowImg(FALSE, FALSE);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}
//(解説:原寸表示とサイズ変更中央表示です。g_Album.ShowImgの引数の違いに注意してください。)

bool CMyWnd::OnRotate0() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_Album.SetRotateFlip(RotateNoneFlipNone);
    OnNorm();
    ChangeRotateFlip(0);
    return TRUE;
}

bool CMyWnd::OnRotate90() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_Album.SetRotateFlip(Rotate90FlipNone);
    OnNorm();
    ChangeRotateFlip(1);
    return TRUE;
}

bool CMyWnd::OnRotate180() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_Album.SetRotateFlip(Rotate180FlipNone);
    OnNorm();
    ChangeRotateFlip(2);
    return TRUE;
}

bool CMyWnd::OnRotate270() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_Album.SetRotateFlip(Rotate270FlipNone);
    OnNorm();
    ChangeRotateFlip(3);
    return TRUE;
}
//(解説:↑の4つは全て同じ処理でenum型のRotateFlipType引数だけ違います。この引数はCALBUM.hに説明を入れていますが、Microsoft Docを確認してみてください。)

bool CMyWnd::OnResize() {

    if(g_Album.GetCount()) {
        resizedlg.DoModal(m_hWnd, "IDD_RESIZE", resizedlgProc, m_hInstance);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}
//(解説:IDD_RESIZEダイアログを呼び出します。フォールダー内の対象写真画像一括処理を行うDLLのResizerとは異なり、単一写真画像処理です。

bool CMyWnd::OnEdit() {

    if(g_Album.GetCount()) {
        //写真の裏書ダイアログを開く
        editdlg.DoModal(m_hWnd, "IDD_EDIT", editdlgProc, m_hInstance);
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}
//(解説:写真の裏書処理です。)

bool CMyWnd::OnDetail() {

    if(g_Album.GetCount()) {
        //写真のプロパティダイアログを開く
        SHELLEXECUTEINFO ShInfo;
        ZeroMemory(&ShInfo, sizeof(SHELLEXECUTEINFO));
        ShInfo.cbSize    = sizeof(SHELLEXECUTEINFO);
        ShInfo.hwnd        = m_hWnd;
        ShInfo.lpVerb    = "properties";
        ShInfo.lpFile    = g_Album.GetFileName();
        ShInfo.fMask    = SEE_MASK_INVOKEIDLIST;
        ShInfo.nShow    = SW_SHOWNORMAL;
        ShellExecuteEx(&ShInfo);
//(解説:今回私も初めて使いましたが、OSサービスのファイルの「プロパティダイアログ」を表示する処理です。)
        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnGather() {

    if(!g_Album.GetCount()) {
        MessageBox(m_hWnd, "写真がありません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //アルバム写真を纏めるフォールダーを取得
    char* cp = g_Cmndlg.GetPath(m_hWnd, "アルバム写真を纏めるフォールダーの指定");
    if(!cp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //ファイルパスを'\'付きで記録
    CSTR path = cp;
    path = path + "\\";
    //fnに纏めフォールダーのパスを代入
    CSTR fn = path;
    //選択写真の記録
    int sel = g_Album.GetSelected();
    //写真数を取得
    int lmt = g_Album.GetCount();
    for(int i = 0; i < lmt; i++) {
        //写真を選択
        g_Album.Select(i);
        //選択写真のフルパスファイル名を取得
        g_Arg = cp = g_Album.GetFileName();
        //fnにファイル名のみ付加
        fn = fn + g_Arg.FileName();
        //ファイルを上書きコピー
        CopyFile(cp, fn.ToChar(), FALSE);
        //fnに纏めフォールダーのパスを代入
        fn = path;
    }
    //選択写真を再選択
    g_Album.Select(sel);
//(解説:実は実際にアルバムを使ってみて、「これは欲しいな」ということで付け足した処理です。アルバムファイルはPC内に散在するファイルを対象にしますが、それを人に渡す等の場合、「一つのフォールダーに(サイズをそろえて)集めたいな」と思うことがあるでしょう。その為の処理です。)
    return TRUE;
}

bool CMyWnd::OnResizer() {

    ResizeImages();
//(解説:あっさりとしていますが、DLLのResizerという1フォールダー内のGDI+が対象とするイメージファイルを大きさの条件を付けて合致するものを縮小してコピー、または上書きするツールです。別途シリーズで解説します。)
    return TRUE;
}

bool CMyWnd::OnHelp() {

    CSTR cmd = "hh \"";
    cmd = cmd + g_Help + "\"";
    WinExec(cmd.ToChar(), SW_SHOWNORMAL);
    return TRUE;
//(解説:ヘルプファイルを開きます。)
}

bool CMyWnd::OnVersion() {

    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc, m_hInstance);
    return TRUE;
}
 

今回は三つ(実は四つ)やってしまいましょう。

先ずはコンパイラーの対象ファイルであるAlbum.cppから。

【Album.cpp】
//////////////////////////////////////////
// Album.cpp
//Copyright (c) 02/28/2022 by BCCSkelton
//////////////////////////////////////////
#include    "Album.h"
#include    "User.h"
#include    "AlbumProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //データファイル付きで起動する場合(ByFileフラグを立てる)
    if(g_Arg.c() > 1)
        g_ByFile = TRUE;
//(解説:いつも通りの外部変数フラグでの対応です。)

    //2重起動防止
    if(!Album.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)
        HWND hWnd = FindWindow("MainWnd", "Album");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

    //ウィンドウ登録 - Init(ClassName, hInstance, WndProc, "IDM_MAIN",
    //                        (以下省略可)MAKEINTRESOURCE(IDI_ICON), IDC_ARROW, Brush)
    Album.Init("MainWnd", hInstance, SDIPROC, "IDM_MAIN", MAKEINTRESOURCE(IDI_ICON));

    //ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
    //                        ExStyle, hParent, hMenu, x, y, w, h)
    //WM_LBUTTONDBLCLKを使うためにウィンドウスタイルにCS_DBLCLKSを追加する
    if(!Album.Create("Album", WS_OVERLAPPEDWINDOW | CS_DBLCLKS))
        return 0L;

    //メッセージループに入る
    return Album.Loop();
}
//(解説:cppファイルはほぼSkeltonWizard通りです。)

一転して、Album.hは長々と続きます。

【Album.h】
//////////////////////////////////////////
// Album.h
// Copyright (c) 02/28/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResAlbum.h"
//CALBUMのヘッダー
#include    "CALBUM.h"
//(解説:当然CALBUM利用の為です。CPHOTOはCALBUMから呼ばれます。)
//遷移用スライダーコントロール関連
#include    "CSLIDER.h"
//(解説:DirectShowでも使ったCSLIDERです。これは前回やったので今回は割愛します。これが「四つ目」でした。)
#define        IDC_SLIDER    1000
//(解説:スライダーのコントロールIDです。ResAlbum.hを見て被らないようにしてください。)
//最小ウィンドウ幅、高さ初期値
#define        MINW    480    //サムネイルのLARGEよりも大きくなければならない
#define        MINH    360    //サムネイルのLARGE + 50(ツールバーとステータスバーの高さ)よりも大きくなければならない

//(解説:これでよいかどうかは分かりません。お好みでどうぞ。)
//サムネイルの幅、高さ
#define        SMALL    128
#define        LARGE    256
//(解説:リソースのメニュ―関係を変更するだけで、更にMEDIUMを入れた3段階にも簡単に変更できます。)
//最大コメント長さ
#define        MAXCMT    1024
//(解説:小説を書こうという訳ではないので、原稿用紙2枚半で十分でしょう。)
//FileList.dllの静的ロード
#include    "DLL.h"
#pragma comment(lib, ".\\Debug\\FileList.lib") 
IMPORT char* GetFileList(char*, char*, char*, char*);
//(解説:DirectShowから使っているFileListのDLL版です。今回のCPHOTOクラスのデータ形が将にぴったりです。なお、DLL.hはFileListの時に説明したので割愛します。これが五つ目か?)
//Resizer.dllの静的ロード
#pragma comment(lib, ".\\Debug\\Resizer.lib") 
IMPORT int ResizeImages();
//(解説:今回このDLLベースのリサイズツールについては触れません。次回取り上げようかと考えています。)
//IMM関係のヘッダーとライブラリー
#include    <imm.h>
#pragma comment(lib, "imm32.lib") 
//(解説:突然ですが、写真裏書ダイアログを日本語モードで開く為に、いつも皆さんが使っている日本語入力インターフェースIMEのベースとなるInput Method Manager (IMM)を使います。)
//アルバムファイルフィルター
#define        FILEFLT    "アルバムファイル(*.alb)\0*.alb\0\0"
//(解説:アルバムファイルの拡張子は"*.alb"にしました。)

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDI
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CSDI(UName) {}
    //メニュー項目関連
    bool OnNew();
    bool OnOpen();
    bool OnSave();
    bool OnSaveas();
    bool OnExit();
    bool OnAdd();
    bool OnDel();
    bool OnThumnail();
    bool OnPrevious();
    bool OnNext();
    bool OnLarge();
    bool OnSmall();
    bool OnNorm();
    bool OnOrigin();
    bool OnAdjust();
    bool OnRotate0();
    bool OnRotate90();
    bool OnRotate180();
    bool OnRotate270();
    bool OnResize();
    bool OnEdit();
    bool OnDetail();
    bool OnGather();
    bool OnResizer();
    bool OnHelp();
    bool OnVersion();
//(解説:全てメニュー項目です。)
    //ウィンドウメッセージ関連
    bool OnCreate(WPARAM, LPARAM);
    bool OnLButtonDown(WPARAM, LPARAM);
    bool OnRButtonDown(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnPaint(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDropFiles(WPARAM, LPARAM);
    bool OnMinMax(WPARAM, LPARAM);
//(解説:最近はまっているドラッグアンドドロップと最小サイズ制限以外では左マウスクリックを使っている点が目新しいですかね。)
    //ユーザー定義関数
    bool ReadAlbum();            //*.albファイルを読む
    bool ShowAlbum(bool);        //アルバムを表示する
    void ChangeState(bool);        //保存メニュー切替
    void ChangeMode(bool);        //表示メニュー切替
    void ChangeRotateFlip(int);        //回転、反転メニュー切替
    void ShowAsIs();            //単一イメージ原寸表示
    void ShowAdjusted();            //単一イメージサイズ調整表示
//(解説:結構内部処理関数が多くなっちゃいました。概要はコメントで大体わかると思います。)
};

////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////
CMyWnd Album("Album");    //ウィンドウクラスインスタンスの生成

BEGIN_SDIMSG(Album)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(Album, IDM_NEW, OnNew())
    ON_COMMAND(Album, IDM_OPEN, OnOpen())
    ON_COMMAND(Album, IDM_SAVE, OnSave())
    ON_COMMAND(Album, IDM_SAVEAS, OnSaveas())
    ON_COMMAND(Album, IDM_EXIT, OnExit())
    ON_COMMAND(Album, IDM_ADD, OnAdd())
    ON_COMMAND(Album, IDM_DEL, OnDel())
    ON_COMMAND(Album, IDM_THUMNAIL, OnThumnail())
    ON_COMMAND(Album, IDM_PREV, OnPrevious())
    ON_COMMAND(Album, IDM_NEXT, OnNext())
    ON_COMMAND(Album, IDM_LARGE, OnLarge())
    ON_COMMAND(Album, IDM_SMALL, OnSmall())
    ON_COMMAND(Album, IDM_NORM, OnNorm())
    ON_COMMAND(Album, IDM_ORIG, OnOrigin())
    ON_COMMAND(Album, IDM_ADJT, OnAdjust())
    ON_COMMAND(Album, IDM_0, OnRotate0())
    ON_COMMAND(Album, IDM_90, OnRotate90())
    ON_COMMAND(Album, IDM_180, OnRotate180())
    ON_COMMAND(Album, IDM_270, OnRotate270())
    ON_COMMAND(Album, IDM_RESIZE, OnResize())
    ON_COMMAND(Album, IDM_EDIT, OnEdit())
    ON_COMMAND(Album, IDM_DETAIL, OnDetail())
    ON_COMMAND(Album, IDM_GATHER, OnGather())
    ON_COMMAND(Album, IDM_RESIZER, OnResizer())
    ON_COMMAND(Album, IDM_HELP, OnHelp())
    ON_COMMAND(Album, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    ON_CREATE(Album)
    ON_LBUTTONDOWN(Album)
    ON_RBUTTONDOWN(Album)
    ON_NOTIFY(Album)
    ON_SIZE(Album)
    ON_PAINT(Album)
    ON_CLOSE(Album)
    ON_(Album, WM_DROPFILES, OnDropFiles(wParam, lParam))
    ON_(Album, WM_GETMINMAXINFO, OnMinMax(wParam, lParam))
END_WNDMSG
//(解説:メンバー関数のベースとなるマクロテーブルです。)

///////////////////
//ツールバーの作成
///////////////////
CTBAR TBar;

///////////////////////
//ステータスバーの作成
///////////////////////
CSBAR SBar;

//////////////////////
//CALBUMのインスタンス
//////////////////////
CALBUM g_Album;
//(解説:これが今回の主役です。)

//////////////////////////////
//遷移用スライダーコントロール
//////////////////////////////
CSLIDER g_Slider;
//(解説:「標準表示モード」だけ使います。「サムネイルモード」使ってもよかったのですが、矢張り「アルバムをめくる」動作感の為に隠しました。)

/////////////////////
//CARGのインスタンス
/////////////////////
CARG g_Arg;

//////////////////////
//CMNDLGのインスタンス
//////////////////////
CMNDLG g_Cmndlg;

//////////////////////
//CEXTCHKのインスタンス
//////////////////////
CEXTCHK g_ExtChk;
//(解説:これら↑三つは殆どどのプログラムでも必要なサービスクラスですね。)

///////////////////////////////////////////
// CDLGクラスからRESIZEDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class RESIZEDLG : public CDLG {
public:
    //メンバー変数
    Image* m_pImage = 0;                //Imageクラスポインター
    int m_ImgW;                            //イメージの幅
    int m_ImgH;                            //イメージの高さ
    char m_NewName[MAX_PATH] = {0};        //セーブ用のファイル名
    WCHAR m_WName[MAX_PATH] = {0};        //ワイド文字ファイル名
    WCHAR m_Encoder[12] = {0};            //エンコーダー型名
    HWND m_hSlider = 0;                    //スライダーコントロールのハンドル
//(解説:一転してRESIZEダイアログはCALBUMでカバーされていない処理が多くて、独自の変数や関数を持つことになります。Imageクラスが画像、様々なファイルタイプのファイルの書き込み用のエンコーダーと画像サイズ調整用のスライダーがあります。)
    //メンバー関数
    bool OnInit(WPARAM, LPARAM);
    bool OnHScroll(WPARAM, LPARAM);
    bool OnIdok();
    bool OnIdcancel();
    //ユーザー定義関数
    int GetEncoderClsid(const WCHAR*, CLSID*);
//(解説:OnHScrollはスライダーからの通知取得用です。またユーザー定義関数のGetEncodeClsid関数はMicrosoft Docに出ていたもの(英語版コメント)をちょっと書き換えた(日本語コメント)ものです。AlbumProc.hで紹介します。)
};

////////////////////////////////////////////////////////////////////////////
// RESIZEDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////
RESIZEDLG resizedlg;

BEGIN_MODALDLGMSG(resizedlgProc, resizedlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(resizedlg, IDOK, OnIdok())
    ON_COMMAND(resizedlg, IDCANCEL, OnIdcancel())
    ON_HSCROLL(resizedlg)
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからEDITDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class EDITDLG : public CDLG {
public:
    //メンバー変数
    int m_Width;        //ダイアログクライアントエリア幅
    int m_Height;        //ダイアログクライアントエリア高さ
    HIMC m_hImc;        //IMEコンテキストハンドル
    //メンバー関数
    bool OnInit(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnMinMax(WPARAM, LPARAM);
    bool OnIdok();
    bool OnIdcancel();
};
//(解説:エディットボックス一つのダイアログですが、ここでもOnMinMax関数で最小サイズ制限を行っています。またダイアログが開くと日本語変換モードにしているので、その為のm_hImcです。)

////////////////////////////////////////////////////////////////////////////
// EDITDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////
EDITDLG editdlg;

BEGIN_MODALDLGMSG(editdlgProc, editdlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(editdlg, IDOK, OnIdok())
    ON_COMMAND(editdlg, IDCANCEL, OnIdcancel())
    ON_SIZE(editdlg)
    ON_(editdlg, WM_GETMINMAXINFO, OnMinMax(wParam, lParam))
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class VERSIONDLG : public CDLG {
public:
    bool OnIdok();
};

////////////////////////////////////////////////////////////////////////////
// VERSIONDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////
VERSIONDLG versiondlg;

BEGIN_MODALDLGMSG(versiondlgProc, versiondlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(versiondlg, IDOK, OnIdok())
END_DLGMSG

如何でしたか?大体何をするのか想像がつきましたか?次はAlbumProc.hを補完するUser.hです。

【User.h】
//////////////////////
//画像回転表示用文字列
//////////////////////
const char* g_RotateFlip[4] = {"0度回転表示", "90度回転表示", "180度回転表示", "270度回転表示"};
//(解説:メニューに合わせた文字列変数配列にしています。)

/////////////////////
//*.albファイル用変数
/////////////////////
CSTR g_FileName;
CSTR g_File;
//(解説:定番ですが、CSTRクラスを使ったファイル名とファイル(データ)読み込み用の変数です。)

/////////////////////
//ファイル起動用変数
/////////////////////
bool g_ByFile;
//(解説:ファイル起動の際のフラグです。)

//////////////////
//AlbumHelp用変数
//////////////////
CSTR g_Help;
//(解説:ヘルプファイルのフルパスファイル名を入れます。)

////////////////////////////////
//AlbumListフォールダーへのパス
////////////////////////////////
CSTR g_Path;
//(解説:アルバムファイルを入れる専用フォールダー用のパスを記録します。面倒なのでCINIクラスを使った*.iniファイルにはしていません。)

///////////////////////
//イメージ表示メニュー
//TRUE-単一写真表示
//FALSE-サムネイル表示
///////////////////////
bool g_ShowMode = FALSE;
//(解説:この表示モードフラグでデフォルトの「サムネイル表示(FALSE)」と「標準表示(TRUE)」を切り替えた時の処理を差配します。)

///////////////////
//サムネイルサイズ
///////////////////
int g_ThumbSize = LARGE;
//(解説:デフォルトのサムネイルサイズは大、です。)

///////////////////////////
//ツールバーのサブクラス化
///////////////////////////
WNDPROC g_TBProc = NULL;

//TOOLBARのメッセージからWM_HSCROLLのみを取得する
LRESULT CALLBACK TOOLBARProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

    int pos;
    switch (uMsg) {
    case WM_HSCROLL:
        if(lParam != (LPARAM)GetDlgItem(hWnd, IDC_SLIDER))
            break;                            //スライダーからの通知でなければ何もしない
        switch(LOWORD(wParam)) {            //スライダーコントロールからの通知
        case SB_THUMBTRACK:                    //スライダーのドラッグ時は何もしない
            return 0;
        case SB_THUMBPOSITION:                //摘みを離した時に
            pos = HIWORD(wParam);            //スライダーの位置を取得
            break;
        default:                            //それ以外の場合、HIWORD(wParam)は使えないので
            pos = g_Slider.GetPosition();    //GetPosition関数で位置を取得する
            break;
        }
        if(g_ShowMode) {
            if(g_Album.GetCount()) {
                //表示イメージの設定
                g_Album.SetCol(255, 255, 255, 255);    //Alph = 255、白色で
                g_Album.Cls();                        //画面クリアー
                g_Album.Select(pos);                //スライダー位置の
                g_Album.ShowImg(FALSE, FALSE);        //写真を画面中央サイズ調整表示
            }
        }
        return 0;
    default:
        break;
    }
    return CallWindowProc(g_TBProc, hWnd, uMsg, wParam, lParam);
}
//(解説:これはDirectShowでやったのと同じ、ツールバーに入れるスライダーコントロールからのメッセージを親のツールバーから、おじいちゃんのメインウィンドウへ移牒する為のものです。という訳で説明は割愛します。)

さて、次回はトリのAlbumProc.hを説明します。