BCCSkeltonを使ったサンプルを"SampleBCCSkelton"というフォールダーに入れていますが、前回(開発はWin98)でいれた古典的ブロック崩しの動作がウィンドウズの進歩とともに正常に動作しなくなったので、代替ゲームとしてLifeGameとMazeを入れています。

 

LifeGameは説明が不要なくらいよく知られたゲーム(というかシミュレーション)で、社会の生成、消滅の過程を過密、過疎を条件に推移させてゆくものです。一方、Mazeはコンピューター好きな人は必ず好きな「迷路」を扱っていて、定年退職後暇だったので、色々とアルゴリズムの勉強をさせていただき、為になりました。

 

一般に迷路はRPGゲームなどの環境として使われますが、迷路に関わるプログラミングは「生成」、「移動と脱出」が主となります。「生成」には「棒倒し法」、「穴掘り法」、「壁伸ばし法」があり、「移動と脱出」には(小学生の時に覚えたのですが)「右手法(左手でもよいのですが...汗;)」などがありますね。(注1)

 

注1:暇つぶしのアルゴリズム勉強でよいサイトを見つけたので、ご興味がある方は覗いてください。

 

私の場合は、まず「生成」の勉強をして、コンソールベースで3つのアルゴリズムを試し、ヘッダーファイルを作成しました。そしてMazeではそのうちの一つである「穴掘り法」を使っています。(MazebyDigging.h)また、「移動、脱出」については迷路にはまって右手法で脱出を試みる「さまよう人」の行動を想定して、クラスヘッダーファイルを作っています。(Wanderer.hWanderer.h)

 

私が1982年にシャープのポケコン(注2)でプログラミングを始めた後、初めて翌年8ビットのMSXを買ったときには取扱説明書にMSX Basicのサンプルプログラムが印刷されており、それを打ち込んで動かしていましたが、その時の一つに「迷路の3D表示」がありました。20年前にはそのBasic codesをC++に直して持っていたのですが、すべて失ってしまったので、今回はそれも試行錯誤でつくっています。(当時のアルゴリズムと違うかもしれません。)

そしてできたのがサンプルの”Maze”で、

(1) 迷路を生成する。

(2) 迷路を表示する。

(3) 迷い人に脱出行を開始させる。(自動)

というだけの単純なシミュレーションですが、(3)を2D(迷路のセルと迷い人をビットマップで表示)と3D(モノクロ線描画)で切り替えられる、というところが特徴でしょうか?

 

しかし、迷路を勉強する際にウェブを漁っていて、BCBで作成された上記3つのアルゴリズムの表示プログラムや、絵画のような美麗な3D表示プログラムを発見しました。ご関心のある方はご自身でウェブの世界を検索してみてください。(きっとゲーム好きの方はご興味があるはずです。)

 

自分でプログラムを作った後、仕様外動作やバグなどでバージョンアップすることはよくありますが、意図的に改善を目的に改良する場合はそれほど多くはありません。

 

この一年で当初のツールのバージョンアップで大幅に使い勝手が改善したと思うのはEZImageなので、その経緯について少し述べます。

 

EZImageはWindowsプログラミング上必然の画像リソース作成、変更を目的に2002年に作成したツールです。ベースはダイアログでCANVASクラスを利用した16x16または32x32のbitmap(モノクロから32bitまで)、Icon、Cursor(当時は256色-8bit迄)を扱えました。しかし、これを実際に使ったことはなかったので、矢鱈色のメニューが多かったり、描画編集機能が限定されていたりして、実際に使い始めて不満に思えることが多くなりました。

 

・ 色なんてカラーダイアログが出て、好きなところからスポイトでとってこれればそれ以外には16色のカラーパレットだけでよい。

・ 描画は、16x16または32x32と小さい画像なので基本は「点描画」でよく、むしろ一定描きあげたところで「部分コピー」、同様のイメージの「反転」、位置間違いの場合の「シフト」などが欲しいな、と感じました。

・ また思ったよりも描画手数が多いので、Undo回数は多い方が良い。

・ 逆に16x16、32x32以外にも48x48や64x64まで掛けたらよいけど、実際には(少なくとも自分では)需要がない。

・ IconやCursorも中身はBitmapなので、画像情報の表示は統一した方が良い。

・ (既にIconViewerのところで書きましたが)Icon、CursorはLoadImageに選択されるよりも、操作者がイメージを選択したい。

・ 現在は最後にIcon、Cursorも32bit迄対応しているので拡張したい。

・ ファイルによる起動、Drag & Drop、ファイル履歴による起動、ショートカットキー等のインターフェースの改善。

 

ちんたらやったので結構時間がかかりましたが、上記はいずれも新仕様となり、現在のVersion 2.0に反映しています。

 

 

まぁ、ユーザーの声が「改良のきっかけ」ということは間違いがなく、「使われるソフト」を作ることが最も大事なことだと考えさせられました。

 

本編の冒頭でIconViewerは、スタンドアロンアプリではなく、他のアプリの補助的なダイアログを視野に入れて開発した、と言いました。そのダイアログがBCCForm and BCCSkeltonのサンプルにあるIconView_DLLです。

 

このIconView_DLLは、IconViewerの機能を絞り、メニューやバーを外し、アイコンのファイルパス・名を与えてダイアログを開き、構成ビットマップを選択するとその順番の番号が返る、というだけのダイアログです。このダイアログを使ったアプリの第一号はEZImageになりました。(以下のイメージでは単一のビットマップなので選択の余地はありませんが、構成ビットマップがn個のアイコンであれば「0 - (n-1)」の選択ビットマップの番号が返ります。)

 

このIconView_DLLのファイルは通常のBCCForm and BCCSkeltonのファイルと異なり、IconViewerのファイルをマニュアルで、

IconViewer.h + IconViewerProc.h → IconView_DLL.h

IconViewer.cpp → IconView_DLL.cpp

にまとめています。("IconView.h"はDLL用のヘッダーファイルです。)

 

次にIconView_DLLプロジェクトをアプリケーションに導入するには3つの方法を説明します。

(1) ソースファイルを使う。(EZImageはこれで作っています。)

(2) 静的DLLとして使う。

(3) 動的DLLとして使う。
(2)と(3)の使い方はIconView_DLLのファイルにあるTestDLLParent.cppを見ていただければよいかと思います。(このファイルで実際に(2)と(3)を別個にコンパイルして試してみることができます。)もちろんEZImageも(2)や(3)(おそらく最も効率的なのは(2)だと思いますが)に書き直すこともできます。

 

BCCForm and BCCSkeltonで書くDLLプログラムの習作として、また自分で開発したプログラムを汎用DLLに書き直すサンプルとして、IconView_DLLが参考になるのではないかと思います。

今回でIconViewerプロジェクトの解説は最後となりますが、IconViewerProc.hで定義された残りの関数を解説してゆきます。今回も(解説:)をご覧ください。

 

///////////////////
//ユーザー定義関数
///////////////////
bool ICONVIEWER::SetListView() {  //(解説:登録されアイコンの構成ビットマップ情報をリストビューに表示します。)

    //リストビューへのデータ登録
    HWND hLV = GetDlgItem(m_hWnd, IDC_LISTVIEW);
    SendMessage(hLV, LVM_DELETEALLITEMS, 0, 0);    //リストビューの全データ抹消
    int IconNum = g_Icon.GetCount();  //(解説:構成ビットマップの数を確認します。)

    //(解説:ローカル変数の宣言です。順にICONDIRECTRYへのポインター、BITMAPINFOHEADERへのポインター、リストビューアイテム構造体と、文字列を扱うBCCSkeltonのCSTRクラス変数です。)
    LPICONDIRENTRY lpIde;
    PBITMAPINFOHEADER pBmi;
    LV_ITEM item;
    CSTR str;
    for(int i = 0; i < IconNum; i++) {  //(解説:構成ビットマップの数だけ繰り返します。)
        //ICONDIRENTRYの情報を取得する
        g_Icon.GetIDEntry(i, &lpIde);
        //リスト先頭列の設定(幅)
        item.mask = LVIF_TEXT;
        str = lpIde->bWidth;  //(解説:ビットマップの幅です。)
        item.pszText = str.ToChar();  //(解説:CSTRクラスの変数データをchar*ポインターとして返す変数です。多用します。)
        item.iItem = i;  //(解説:i番目の行を意味します。以下i行が終わるまで有効です。)
        item.iSubItem = 0;  //(解説:0番目の列を意味します。)
        SendMessage(hLV, LVM_INSERTITEM, 0, (LPARAM)&item);  //(解説:i番目の行と0番目の列の交差セルの文字列を代入します。)
        //第2列の設定(高さ)
        str = lpIde->bHeight;
        item.pszText = str.ToChar();
        item.iSubItem = 1;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第3列の設定(色数)0=256色  (解説:この1byteには2色(モノクロ)か16色のみで、pBmi->biBitCountが8以上は0となります。)
        str = lpIde->bColorCount;
        item.pszText = str.ToChar();
        item.iSubItem = 2;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第4列の設定(色数)BitCount
        str = lpIde->wBitCount;
        item.pszText = str.ToChar();
        item.iSubItem = 3;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第5列の設定(画像データ部の大きさ) (解説:BITMAPINFOHEADER、RGBQUAD、ビットパターンの合計です。) 
        str = lpIde->dwBytesInRes;
        item.pszText = str.ToChar();
        item.iSubItem = 4;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第6列の設定(ファイル先頭から画像データまでのオフセット)
        str = lpIde->dwImageOffset;
        item.pszText = str.ToChar();
        item.iSubItem = 5;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //BITMAPINFOHEADERの情報を取得する
        g_Icon.GetBmpInfo(i, &pBmi);  //(解説:今まではICONDIRENTRY構造体の情報でしたが、以下はBITMAPINFOHEADERの情報です。)
        //第7列の設定(画像の幅)
        str = pBmi->biWidth;
        item.pszText = str.ToChar();
        item.iSubItem = 6;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第8列の設定(画像の高さ×2 の値)  //(解説:XORビットマップとANDビットマップがあるからです。)
        str = pBmi->biHeight;
        item.pszText = str.ToChar();
        item.iSubItem = 7;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第9列の設定(プレーンの数)
        str = pBmi->biPlanes;
        item.pszText = str.ToChar();
        item.iSubItem = 8;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第10列の設定(ピクセル当りの色数・1, 4, 8(, 16), 24(, 32)のいずれか)
        str = pBmi->biBitCount;
        item.pszText = str.ToChar();
        item.iSubItem = 9;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
        //第11列の設定(表示データ部とマスクデータ部の合計サイズ)
        str = pBmi->biSizeImage;
        item.pszText = str.ToChar();
        item.iSubItem = 10;
        SendMessage(hLV, LVM_SETITEM, 0, (LPARAM)&item);
    }

    //(解説:bool関数なので本来 "return" が必要です。bcc32cであればエラーか警告が出ますね。)
}

bool ICONVIEWER::ShowIconData(int i) {  //(解説:エディットボックスに詳細情報を文字列で表示する関数です。)

    PBITMAPINFOHEADER Bmi;  (解説:BITMAPINFOHEADERへのポインター。)
    LPICONDIRENTRY IDE;  //(解説:ICONDIRENTRYへのポインター。)
    g_Icon.GetBmpInfo(i, &Bmi);
    g_Icon.GetIDEntry(i, &IDE);
    CSTR IconInfo = "【ICONDIR】        : 6bytes\r\n";  //(解説:このIconInfoというCSTR変数にどんどん文字列を足してゆきます。"\r\n"は復帰改行の意味です。)
    //アイコン基本情報の表示
    if(g_ImageType == IMAGE_ICON)  //(解説:アイコンとカーソルで情報が異なる場合、このようにif節を使って文字列を追加します。)
        IconInfo = IconInfo + "ファイル種類        : ICON\r\n";
    else if(g_ImageType == IMAGE_CURSOR)
        IconInfo = IconInfo + "ファイル種類        : CURSOR\r\n";
    else
        return FALSE;
    IconInfo = IconInfo + "画像数            : ";
    IconInfo = IconInfo + g_Icon.GetCount();  //(解説:CSTRには '+' で文字列のみならず、整数も文字列として追加できます。)
    IconInfo = IconInfo + "\r\n";
    IconInfo = IconInfo + "【ICONDIRENTRY】        : 16bytes\r\n";
    IconInfo = IconInfo + "イメージ幅            : ";
    IconInfo = IconInfo + IDE->bWidth;
    IconInfo = IconInfo + "\r\n";
    IconInfo = IconInfo + "イメージ高            : ";
    IconInfo = IconInfo + IDE->bHeight;
    IconInfo = IconInfo + "\r\n";
    IconInfo = IconInfo + "色数            : ";
    IconInfo = IconInfo + IDE->bColorCount;
    IconInfo = IconInfo + " (wBitCount = ";
    IconInfo = IconInfo + IDE->wBitCount;
    IconInfo = IconInfo + ")\r\n";
    IconInfo = IconInfo + "ファイル先頭からのオフセット    : ";
    IconInfo = IconInfo + IDE->dwImageOffset;
    IconInfo = IconInfo + "\r\n";
    IconInfo = IconInfo + "画像データ部サイズ (Bytes)    : ";
    IconInfo = IconInfo + IDE->dwBytesInRes;
    IconInfo = IconInfo + "\r\n";
    IconInfo = IconInfo + "【BIMAPINFOHEADER】    : 40bytes\r\n";
    IconInfo = IconInfo + "1画素当たりのビット数        : ";
    IconInfo = IconInfo + Bmi->biBitCount;
    IconInfo = IconInfo + "\r\n";
    IconInfo = IconInfo + "(RGBQUADテーブル)    : ";
    if(Bmi->biBitCount > 8)  //(解説:カラービットが8を超えるビットマップにはRGBQUADカラーパレットがありません。)
        IconInfo = IconInfo + "0";
    else
        IconInfo = IconInfo + 4 * (1 << Bmi->biBitCount);  //(解説:QGBQUADは4bitで、モノクロ(biBitCount = 1)は2、4bitは16、8bitは256個のRGBQUADがあります。)
    IconInfo = IconInfo     + "bytes (4 x 2 ^";
    IconInfo = IconInfo + Bmi->biBitCount;
    IconInfo = IconInfo     + ")\r\n";
    IconInfo = IconInfo + "(カラービット hbmColor)    : ";
    IconInfo = IconInfo + ((Bmi->biBitCount * Bmi->biWidth + 31) / 32) * 4 * abs(Bmi->biHeight / 2);
    IconInfo = IconInfo + "bytes\r\n";
    IconInfo = IconInfo + "(マスクビット hbmMask)    : ";
    IconInfo = IconInfo + ((Bmi->biWidth + 31) / 32) * 4 * abs(Bmi->biHeight / 2);
    IconInfo = IconInfo + "bytes\r\n";

    IconInfo = IconInfo + "合計イメージサイズ        : ";
    IconInfo = IconInfo + Bmi->biSizeImage;
    IconInfo = IconInfo + "bytes\r\n";
    //マスクビットのイメージをプリント
    IconInfo = IconInfo + "---------- マスクビットイメージ ----------\r\n";
    IconInfo = IconInfo + "No 01234567 01234567 01234567 01234567\r\n";
    IconInfo = IconInfo + "--------------------------------------\r\n";
    int j, k, l;
    char Dat[4];
    if(Bmi->biHeight > 0) {    //ボトムアップ
        for(j = 31; j >= 0; j--) {
            wsprintf(Dat, "%02d ", j);
            IconInfo = IconInfo + Dat;
            for(k = 0; k < 4; k++) {
                for(l = 0; l < 8; l++) {
                    if(g_Icon.m_AND[i][j * 4 + k] & (1 << (7 - l)))
                        IconInfo = IconInfo + "1";
                    else
                        IconInfo = IconInfo + "0";
                }
                IconInfo = IconInfo + " ";
            }
            IconInfo = IconInfo + "\r\n";
        }
    }
    else {                    //トップダウン
        for(j = 0; j < 32; j++) {
            wsprintf(Dat, "%02d", j);
            IconInfo = IconInfo + Dat;
            for(k = 0; k < 4; k++) {
                for(l = 0; l < 8; l++) {
                    if(g_Icon.m_AND[i][j * 4 + k] & (1 << (7 - l)))
                        IconInfo = IconInfo + "1";
                    else
                        IconInfo = IconInfo + "0";
                }
                IconInfo = IconInfo + " ";
            }
            IconInfo = IconInfo + "\r\n";
        }
    }
    return SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)IconInfo.ToChar());  //(解説:ここではちゃんとreturnしていますね。)
}

///////////////////////////////
//SAVEDLGダイアログの関数定義
///////////////////////////////
bool SAVEDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //ラジオボックスとホットスポットエディットボックスの初期設定  (解説:Ver1.1Aではホットスポット入力はこのダイアログでしたので。汗;)
    switch(g_ImageType) {
    case IMAGE_ICON:    //アイコン
        SendItemMsg(IDC_RBICON, BM_SETCHECK, BST_CHECKED, 0);
        break;
    case IMAGE_CURSOR:    //カーソル
        SendItemMsg(IDC_RBCUR, BM_SETCHECK, BST_CHECKED, 0);
        break;
    }
    return TRUE;
}

bool SAVEDLG::OnIdok() {

    if(SendItemMsg(IDC_RBICON, BM_GETCHECK, 0, 0) == BST_CHECKED)  //(解説:チェックボックスの状態でイメージタイプを記録するグローバル変数に値を代入しています。)
        g_ImageType = IMAGE_ICON;
    else
        g_ImageType = IMAGE_CURSOR;
    EndModal(TRUE);  //(解説:モーダルダイアログの終了です。)
    return TRUE;
}

/////////////////////////////////
//HOTSPOTDLGダイアログの関数定義
/////////////////////////////////
bool HOTSPOTDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //入力文字を3文字に制限する
    SendItemMsg(IDC_HOTSPOTX, EM_SETLIMITTEXT, 2, 0);
    SendItemMsg(IDC_HOTSPOTY, EM_SETLIMITTEXT, 2, 0);
    //(解説:アイコンが読み込まれていればそのデータを表示します。)

    char num[3];
    wsprintf(num, "%d", g_Icon.GetHsX(g_Num));
    SendItemMsg(IDC_HOTSPOTX, WM_SETTEXT, 0, (LPARAM)num);
    wsprintf(num, "%d", g_Icon.GetHsY(g_Num));
    SendItemMsg(IDC_HOTSPOTY, WM_SETTEXT, 0, (LPARAM)num);
    return TRUE;
}

bool HOTSPOTDLG::OnIdok() {

    int n;
    char num[3];
    SendItemMsg(IDC_HOTSPOTX, WM_GETTEXT, 3, (LPARAM)num);
    n = atoi(num);  //(解説:整数文字列を整数に変換します。)
    if(n < 0 || n > 32) {  //(解説:異常値対応です。)
        MessageBox(m_hWnd, "異常値(負または32超)になっています", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    else
        g_Icon.SetHsX(g_Num, n);

        //(解説:突然出てきましたが、これはIconViewer.h宣言ファイルで"int g_Num;    //書き込み処理をしているカーソルの番号"として定義されています。)
    SendItemMsg(IDC_HOTSPOTY, WM_GETTEXT, 3, (LPARAM)num);
    n = atoi(num);
    if(n < 0 || n > 32) {
         MessageBox(m_hWnd, "異常値(負または32超)になっています", "エラー", MB_OK | MB_ICONERROR);
         return FALSE;
    }
    else
        g_Icon.SetHsY(g_Num, n);
    EndModal(TRUE);
    return TRUE;
}

/////////////////////////////////
//VERSIONDLGダイアログの関数定義
//コントロール関数
/////////////////////////////////
bool VERSIONDLG::OnIdok() {  //(解説:ヴァージョン表示ダイアログは表示されたダイアログを消すだけです。)

    EndModal(TRUE);
    return TRUE;
}

 

これでIconViewerプロジェクトのBCCSkeltonファイルの解説を終了します。お役に立てたならば幸いです。

 

今回は(コントロールはないので)メニュー関連関数を解説します。いつも通り、解説は(解説:)で入れましょう。

 

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

    if(g_ByFile)
        g_ByFile = FALSE;  //(解説:デフォルトのFALSE状態に戻し、g_FileNameに入れておいたファイルを読みに行く、ということです。)
    else {
        char* cp = cmndlg.GetFileName(m_hWnd, "アイコンファイル(*.ico)\0*.ico\0カーソルファイル(*.cur)\0*.cur\0\0", TRUE);
        if(cp)
            g_FileName = cp;    //cpがNULLでなければファイル名をg_FileNameへコピー
        else {
            MessageBox(m_hWnd, "操作はキャンセルされました", "メッセージ", MB_OK | MB_ICONERROR);
            return FALSE;
        }  //(解説:コモンダイアログクラスのファイルを開くダイアログを利用しています。戻り値のファイルパス・名がなければエラーとなります。)
    }
    //ファイル読込処理
    if(strstr(g_FileName.ToChar(), ".ico") || strstr(g_FileName.ToChar(), ".ICO")) {    //Iconファイル
        g_ImageType = IMAGE_ICON;
        if(!g_Icon.Load_Icon(g_FileName.ToChar(), IMAGE_ICON, TRUE)) {
            MessageBox(m_hWnd, "アイコンファイルを読み込めませんでした", "エラー", MB_OK | MB_ICONSTOP);
            return FALSE;
        }
    }

    else if(strstr(g_FileName.ToChar(), ".cur") || strstr(g_FileName.ToChar(), ".CUR")) {    //Cursorファイル
        g_ImageType = IMAGE_CURSOR;
        if(!g_Icon.Load_Icon(g_FileName.ToChar(), IMAGE_CURSOR, TRUE)) {
            MessageBox(m_hWnd, "カーソルファイルを読み込めませんでした", "エラー", MB_OK | MB_ICONSTOP);
            return FALSE;
        }
    }

    //(解説:ファイル名拡張子をチェックしてiconかcursorか判断し、MicroSoftの定義するイメージファイル区分をグローバル変数に代入します。今思えば最初のif節はg_FileTypeだけにして、並列したif節でLoad_Icon関数を使ってもよかったですね。サイズの無駄でした。)
    //アイコンを表示
    SendItemMsg(IDC_ICONVIEW, STM_SETIMAGE, IMAGE_ICON, (LPARAM)g_Icon.GetIcon());  //(解説:SendDlgItemMessageのBCCSkelton版です。)
    //リストビューに登録ビットマップ情報を表示
    SetListView();  //(解説:ユーザー定義関数です。次回やります。)
    //ファイル名をステータスバーに表示
    SBar.SetText(1, g_FileName.ToChar());
    SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_FileName.ToChar());    //ToolTipをつける
    //(解説:ステータスバーの処理はワンパターンです。)

    return TRUE;
}

bool ICONVIEWER::OnSave() {

    if(!*g_FileName.ToChar())  //(解説:ファイル名を入れておくグローバル変数がNULLならOnSaveas()関数へ転送します。)
        OnSaveas();
    else
        g_Icon.Save_Icon(g_FileName.ToChar(), g_ImageType);  //(解説:Save_Icon()関数ですが、g_ImageType引数でcursorも識別します。)
    return TRUE;
}

bool ICONVIEWER::OnSaveas() {

    char *flt, *ext;  //(解説:flt文字列ポインターはファイルを開くダイアログのフィルター用、extは拡張子用です。)
    //IDD_SAVEダイアログの表示
    savedlg.DoModal(m_hWnd, "IDD_SAVE", savedlgProc);  //(解説:これがBCCSkeltonのモーダルダイアログ呼び出しです。IDD_SAVEでg_ImageTyeが選択されます。)
    if(g_ImageType == IMAGE_ICON) {
        flt = "アイコンファイル(*.ico)\0*.ico\0\0";
        ext = "ico";
    }
    else {
        flt = "カーソルファイル(*.cur)\0*.cur\0\0";
        ext = "cur";
        int num = g_Icon.GetCount();
        for(int i = 0; i < num; i++) {
            //ビットマップを表示
            SendItemMsg(IDC_BMPVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetColBmp(i));
            SendItemMsg(IDC_MSKVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetMaskBmp(i));
            //ホットスポットの設定
            g_Num = i;
            hotspotdlg.DoModal(m_hWnd, "IDD_HOTSPOT", hotspotdlgProc);
        }  //(解説:イメージ区分でフィルターと拡張子を設定するのと、カーソルの場合、すべてのビットマップでIDD_HOTSPOTダイアログを呼び出してhotspotを入力させます。)
    }
    char* cp = cmndlg.GetFileName(m_hWnd, flt, FALSE, ext);
    if(cp)
        g_FileName = cp;    //cpがNULLでなければファイル名をg_FileNameへコピー
    else {
        MessageBox(m_hWnd, "操作はキャンセルされました", "メッセージ", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_Icon.Save_Icon(g_FileName.ToChar(), g_ImageType);  //(解説:ここで保存します。)
    //ファイル名をステータスバーに表示
    SBar.SetText(1, g_FileName.ToChar());
    SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_FileName.ToChar());    //ToolTipをつける
    return TRUE;
}

bool ICONVIEWER::OnBmpsave() {

    int n = ListView_GetNextItem(GetDlgItem(m_hWnd, IDC_LISTVIEW), -1,
                                LVNI_ALL | LVIS_SELECTED);  //(解説:リストビューの選択状況をチェック。)
    if(n == -1) {    //未選択なら何もしない
        MessageBox(m_hWnd, "ビットマップが選択されていません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    else {            //選択されている場合
        char* flt = "ビットマップファイル(*.bmp)\0*.bmp\0\0";
        char* ext = "bmp";
        char* cp = cmndlg.GetFileName(m_hWnd, flt, FALSE, ext);
        if(!cp) {
            MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }  //(解説:フィルター、拡張子、コモンダイアログクラスの使い方はもうやりました。)
        CBMP bmp(g_Icon.GetColBmp(n));
        bmp.SaveBMP(cp);

        //(解説:ローカル変数としてCBMPクラスのインスタンスbmpをCICONクラスインスタンスのg_Iconのn番目のカラービットマップのハンドルを取得するGetColBmp(n)を与えて作成し、ビットマップを保存します。とても簡単ですね。)
    }
     return TRUE;
}

bool ICONVIEWER::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);  //(解説:OnExit()関数の定番です。)
    return TRUE;
}

bool ICONVIEWER::OnAdd() {

    char* flt = "ビットマップファイル(*.bmp)\0*.bmp\0\0";
    char* cp = cmndlg.GetFileName(m_hWnd, flt, TRUE);
    if(!cp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }  //(解説:コモンダイアログクラスの定番処理です。)
    int n = g_Icon.GetCount();        //個数は次のビットマップカウンターと同じ
    if(!g_Icon.Add_Bitmap(cp))        //ビットマップを追加する
        return FALSE;  //(解説:g_Icon.Add_BitmapがFALSEを返した場合、!でTRUEにしてここで退場です。)
    //ビットマップを表示  (解説:ダイアログにカラービットマップ、マスクビットマップを表示します。)
    SendItemMsg(IDC_BMPVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetColBmp(n));
    SendItemMsg(IDC_MSKVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetMaskBmp(n));

    //エディットボックスの表示
    ShowIconData(n);  //(解説:これもユーザー定義関数で、次回やります。)
    //リストビューに登録ビットマップ情報を表示
    SetListView();
    return TRUE;
}

bool ICONVIEWER::OnDelete() {

    int n = ListView_GetNextItem(GetDlgItem(m_hWnd, IDC_LISTVIEW), -1,
                                LVNI_ALL | LVIS_SELECTED);  //(解説:リストビューの選択確認です。)
    if(n == -1) {
        MessageBox(m_hWnd, "ビットマップが選択されていません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_Icon.Del_Bitmap(n);  //(解説:アイコンの構成ビットマップのn番目を削除します。)
    //表示消去用のビットマップ  (解説:削除したのだから表示し続けると格好悪いですよね?で無地ビットマップを作って消します。)
    HDC hDC = GetDC(m_hWnd);
    HBITMAP hNull = CreateCompatibleBitmap(hDC, 32, 32);
    ReleaseDC(m_hWnd, hDC);
    //消去用ビットマップを表示
    SendItemMsg(IDC_BMPVIEW, STM_SETIMAGE, 0, (LPARAM)hNull);
    SendItemMsg(IDC_MSKVIEW, STM_SETIMAGE, 0, (LPARAM)hNull);
    DeleteObject(hNull);
    //エディットボックスの表示
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)0);  //(解説:削除したビットマップの詳細データも消去します。)
    //リストビューに登録ビットマップ情報を表示
    SetListView();  //(解説:削除-前詰めした後no-リストビューを表示します。)
    return TRUE;
}

bool ICONVIEWER::OnDisplay() {

    int n = ListView_GetNextItem(GetDlgItem(m_hWnd, IDC_LISTVIEW), -1,
                                LVNI_ALL | LVIS_SELECTED);
    if(n== -1) {
        MessageBox(m_hWnd, "ビットマップが選択されていません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //ビットマップを表示
    SendItemMsg(IDC_BMPVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetColBmp(n));
    SendItemMsg(IDC_MSKVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetMaskBmp(n));
    //エディットボックスの表示
    ShowIconData(n);
     return TRUE;
}  //(解説:リストビューを選択したときの動作を行うだけです。-実は元々の仕様がダブルクリックで表示させていたのでその名残です。汗;)

bool ICONVIEWER::OnHelp() {

    //(解説:文字列を扱うCSTRクラスの使い方としてみてください。)
    CSTR cmd = "hh \"";  //(解説:MicrosoftのHTMLヘルプ(hh.exe)を使います。パスが通っているのでこれで大丈夫です。スペースの後の'\"'は、ファイルパスにスペースがあることが考えられるので、二重コンマを作るためです。)
    cmd = cmd + g_HelpFile + "\"";  //(解説:上の文字列にOnInit()関数で設定したヘルプファイルパス・名を追加します。この結果、cmdは"hh \"ヘルプファイルのパス・名.chm\""→「hh "ヘルプファイルのパス・名.chm"」状態を、次のWinExec()関数でcommand.comに与えます。)
    WinExec(cmd.ToChar(), SW_SHOWNORMAL);
    return TRUE;
}

bool ICONVIEWER::OnVersion() {

    //IDD_VERSIONダイアログの表示
    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc);  //(解説:バージョンダイアログを呼び出します。)
    return TRUE;
}

 

今回はここまでです。

前回はOnInit()をしっかりやりました。今回はウィンドウメッセージ割り込み関数の残りを説明します。解説はまた(解説:)で書きます。

 

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

    //ツールバーのツールチップ処理

    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {
        switch(((LPTOOLTIPTEXT)lParam)->hdr.idFrom) {
        case IDM_OPEN:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "アイコンを開く";
            break;
        case IDM_SAVE:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "アイコンの保存";
            break;
        case IDM_BMPSAVE:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "構成ビットマップの保存";
            break;
        case IDM_EXIT:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "終了";
            break;
        case IDM_ADD:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "イメージの追加";
            break;
        case IDM_DELETE:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "イメージの削除";
            break;
        case IDM_DISPLAY:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "ビットマップの表示";
            break;
        case IDM_HELP:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "ヘルプ";
            break;
        case IDM_VERSION:
            ((LPTOOLTIPTEXT)lParam)->lpszText = "バージョン情報";
            break;
        }
        return TRUE;
    }

    //(解説:他にもコードの仕方がありますが、以下は経験上最もコード量が少ないと思います。文字定数の部分は、メニューを含めてストリングテーブルに纏めて使うとメンテが楽です。)
    else if((int)wParam == IDC_LISTVIEW) {    //リストビューからのメッセージ

    //(解説:割り込みがリストビューの物がこのブロックになります。)
        if(((LPNMLISTVIEW)lParam)->hdr.code == NM_CLICK){

        //(解説:リストビュー上での(左)クリックの場合です。選択行のチェックを行います。未選択は-1が返ります。)
            int n = ListView_GetNextItem(GetDlgItem(m_hWnd, IDC_LISTVIEW), -1,
                                        LVNI_ALL | LVIS_SELECTED);
            if(n== -1) {
                MessageBox(m_hWnd, "選択されていません", "エラー", MB_OK | MB_ICONERROR);
                return FALSE;
            }

        //(解説:一行選択がされえ散ればその行のデータのカラー(XOR)ビットマップとマスク(AND)美とマップが表示されます。)
            //ビットマップを表示
            SendItemMsg(IDC_BMPVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetColBmp(n));
            SendItemMsg(IDC_MSKVIEW, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_Icon.GetMaskBmp(n));
            //エディットボックスの表示  (解説:同時にエディットボックスに詳細情報を表示するユーザー関数を読んでいます。)
            ShowIconData(n);
        }
        else if(((LPNMLISTVIEW)lParam)->hdr.code == NM_RCLICK) {

        //(解説:ここからは「編集」メニューのポップアップを行います。)
            //ウインドウの位置情報を取得
            RECT rec;
            GetWindowRect(m_hWnd, &rec);
            //ポップアップメニューの表示場所を設定
            POINT pt;
            GetCursorPos(&pt);
            //ポップアップメニューの表示
            TrackPopupMenu(g_hPopup, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, m_hWnd, &rec);
        }
        return TRUE;
    }
    return FALSE;
}

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

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",  //(解説:終了確認はWM_CKLOSEメッセージで行います。)
                    MB_YESNO | MB_ICONINFORMATION) == IDYES) {
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        EndModal(TRUE);  //今回判明したミスの修正で、ModalDialogベースなのでこの関数を追加しています。)
        return TRUE;
    }
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

bool ICONVIEWER::OnDropFiles(WPARAM wParam, LPARAM lParam) {
    /*(解説:これも定番処理です。今まで書いてきたドラッグアンドドロップの処理を復習します。

        (1)ヘッダーファイルのコールバック関数のマクロテーブルでWM_DROPFILESメッセージ処理用の関数(OnDropFile)を用意する。

              例:"ON_(IconViewer, WM_DROPFILES, OnDropFiles(wParam, lParam))"

        (2)ウィンドウ(OnCreate)やダイアログ(OnInit)の生成時にDragAcceptFiles()関数を実行。

        (3)マクロテーブルで定めた関数(OnDropFiles)で以下の処理を行う。
    */

    //典型的なドラッグアンドドロップ処理
    char FileName[MAX_PATH];
    HDROP hDrop = (HDROP)wParam;                     //HDROPを取得
    DragQueryFile(hDrop, 0, FileName, MAX_PATH);    //ファイル名を取得
    g_FileName = FileName;  //(解説:本プログラムでは読み込みファイル文字列を記録するのグローバル変数へ代入している。)
    g_ByFile = TRUE;    //その後、フラグを立てて「ファイルを開く」の関数ヘ飛ぶ。
    OnOpen();
    DragFinish(hDrop);                                //終了処理

    return TRUE;
}

//ダイアログベースの場合はこれが必要
bool ICONVIEWER::OnDestroy(WPARAM wPram, LPARAM lParam) {

    PostQuitMessage(0);
    return TRUE;
}

 

今回はここまで。

 

今回はいよいよIconViewer(プロジェクト名)Proc.hの解説を行います。

 

本ファイルは、IconViewer.hで宣言したCMyWnd(今回はIconViewerに改名)インスタンスや関連ダイアログのメンバー関数の実装(定義)を行い、従ってプログラムの動作や作法が決まる重要なファイルです。既にSkeltonWizardにより、選択されたウィンドウメッセージやメニュー項目、コントロールの割り込みによる関数もドンガラができており、書けるだけのコードは自動作成されているので、その中にWin32APIを使ったコードを書いて(追加、修正)ゆきます。

このファイルのプログラミングは長いので、今回は初回ということで、まずダイアログが生成される際にWM_INITDIALOGが送られた場合に呼ばれるOnInit()関数(注1)

注1:ウィンドウベースの場合にはWM_CREATE-OnCreate()関数になる。

を見てゆきましょう。いつもの通り、解説文は(解説:)として挿入します。

 

//////////////////////////////////////////
// IconViewerProc.h
// Copyright (c) 04/09/2021 by BCCSkelton
//////////////////////////////////////////

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

    //コモンコントロールの初期化
    InitCommonControls();  //(解説:コモンコントロールを使う際に不可欠。自動作成コードです。)
    //ツールバー登録-SetHandle(hWnd))
    TBar.SetHandle(GetDlgItem(m_hWnd, IDC_TOOLBAR));  //(解説:TBARインスタンスをIconViewerダイアログのツールバーに連携します。自動作成コード。)
    //ツールバーボタン用カスタムビットマップ追加
    TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TBAR), 9);  //(解説:ツールバーにビットマップを9個使うよ、という予告。自動作成コード。)
    //ツールバーボタン追加
    TBBUTTON tbb[13];  //(解説:ツールバーボタンは13個ということで、13 - 9 = 4がスペースになります。自動作成コード。)
    ZeroMemory(tbb, sizeof(tbb));  //(解説:TBBUTTON構造体のtbb配列を0で初期化します。自動作成コード。)

    //(解説:以下は自動作成コードですが、マニュアルで修正する可能性が高いのでやや詳しく述べます。)
    tbb[0].iBitmap = TBar.m_id + 0;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_OPEN;
    //(解説:タブボタン用構造体TBBUTTON配列の1番目tbb[0]に16x15サイズのビットマップの1番目(TBar.m_id + 0)を設定し、「使用可能」状態にし-使用不可で始めたい場合、この1行を削除してください-、ボタンスタイルにしてから、メニュー項目のIDM_OPENに連携させます。以下同様です。例えばIDI_TBARビットマップの変更などによりマニュアルで修正する場合、AddBmp関数のビットマップ数を修正し、tbb配列長を修正し、変更したビットマップボタンに合わせて"TBar.m_id + n"to"tbb[n]"を修正してゆきます。)

    tbb[1].iBitmap = TBar.m_id + 1;
    tbb[1].fsState = TBSTATE_ENABLED;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = IDM_SAVE;
    tbb[2].iBitmap = TBar.m_id + 2;
    tbb[2].fsState = TBSTATE_ENABLED;
    tbb[2].fsStyle = TBSTYLE_BUTTON;
    tbb[2].idCommand = IDM_BMPSAVE;

    tbb[3].fsStyle = TBSTYLE_SEP;    //セパレーター
    //(解説:セパレーターの場合にはこれ一行で完了です。セパレーターを追加するにはtbb配列を追加して、tbb[n]も順次修正してください。自動作成コード。)
    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].fsState = TBSTATE_ENABLED;
    tbb[7].fsStyle = TBSTYLE_BUTTON;
    tbb[7].idCommand = IDM_DELETE;
    tbb[8].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[9].iBitmap = TBar.m_id + 6;
    tbb[9].fsState = TBSTATE_ENABLED;
    tbb[9].fsStyle = TBSTYLE_BUTTON;
    tbb[9].idCommand = IDM_DISPLAY;
    tbb[10].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[11].iBitmap = TBar.m_id + 7;
    tbb[11].fsState = TBSTATE_ENABLED;
    tbb[11].fsStyle = TBSTYLE_BUTTON;
    tbb[11].idCommand = IDM_HELP;
    tbb[12].iBitmap = TBar.m_id + 8;
    tbb[12].fsState = TBSTATE_ENABLED;
    tbb[12].fsStyle = TBSTYLE_BUTTON;
    tbb[12].idCommand = IDM_VERSION;
    TBar.AddButtons(13, tbb);  //(解説:最後にTBBUTTON tbb[n]の添字とAddButtonsの第1引数を同じであるかチェックしてくださいね。なお設定の際の添字は0ベースなのでn-1になります。)

    //ステータスバー登録-SetHandle(hWnd))  (解説:ステータスバーはHelloWorldプロジェクトでやりましたので、おさらいです。)
    SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[2] = {120, -1};  //(解説:自動作成コードは{200, 400}になっているので、幅をマニュアルで調整します。最後の"-1"はステータスバーの残り一杯迄、の意味です。)
    SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "IconViewer Ver2.0");  //(解説:この後自動作成コード、"SBar.SetText(1, "");"がありましたが、削除しました。
    //ファイルパスの設定
    g_HelpFile = Arg.Path();  //(解説:CARGクラスインスタンスのArgで、IconViewer.exeのファイルパスを取得します。
    g_HelpFile = g_HelpFile + "\\IconViewerHelp.chm";  //(解説:g_HwelpFileはCSTRクラス変数なので"="で代入でき、"+"で文字列定数を一つ追加できます。なお、ファイルパスの'\'はC/C++文字列定数では"\\"と表記しないと分かりづらいバグになりますのでご注意を。)
    //CICONインスタンスの初期化
    g_Icon.SetWindow(m_hWnd); //(解説:前回解説したCICONクラスのインスタンスにウィンドウ(ダイアログ)を連携させます。)
    //リストビュー拡張スタイルの設定

   //(解説:以下はBCCSkeltonのCLISTVIEWクラスを使わずにWin32APIで書いてます。詳しくは説明しませんが、表示をICC_LISTVIEW_CLASSESにし、スタイルに一行選択とグリッド線を追加(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES)にしています。なお、今気が付いたのですが(汗;)SendItemMsg(BCCSkeltonのSendDlgItemMessageと等価関数)の行とListView_SetExtendedListViewStyleExマクロがダブって書かれていますね。これ同じことです。(汗汗;;))

    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);
    ListView_SetExtendedListViewStyleEx(GetDlgItem(m_hWnd, IDC_LISTVIEW), LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
    //リストビューの初期化  (解説:リストビューに11列設け、タイトルと幅を設定し、左寄せにします。)
    char* column[11] = {"bWidth", "bHeight", "bColorCount", "dwBitCount",
                        "dwBytesInRes", "dwImageOffset", "biWidth",
                        "biHeight", "biPlanes", "biBitCount", "biSizeImage"};
    int w[11] = {60, 60, 80, 80, 90, 100, 60, 60, 60, 80, 80};
    LV_COLUMN lvcol;
    lvcol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    lvcol.fmt = LVCFMT_LEFT;
    for(int i = 0; i < 11; i++) {
        lvcol.cx = w[i];
        lvcol.pszText = column[i];
        lvcol.iSubItem = i;
        SendItemMsg(IDC_LISTVIEW, LVM_INSERTCOLUMN, i, (LPARAM)&lvcol);
    }
     //ポップアップメニューの設定  (解説:リストビューの右クリックにより「編集」メニューがポップアップするようにハンドルを取得します。)
    g_hPopup = GetSubMenu(GetMenu(m_hWnd), 1);
    //データファイルで起動された場合、ファイルをオープンする  (解説:何度か書いたファイル起動やD&D用のフラグです。TRUEであれば第2引数のファイルパス・名をグローバル変数に記録して「ファイルを開く」処理を行います。)
    if(g_ByFile) {
        g_FileName = Arg.v(1);
        OnOpen();
    }
    //ドラッグアンドドロップを開始する  (解説:不可欠のドラッグアンドドロップ作法の一つです。覚えてくださいね。)
    DragAcceptFiles(m_hWnd, TRUE);
    return TRUE;
}

 

今回はここまで。

タイトルは「ウムッ」という感じですが、内容はとても簡単です。まず、IconViewerプロジェクトのCICON.hを開いてください。

 

1.結論-総論

要すればこのクラスはアイコン(カーソル)ファイルを読み込んで、その構成データ(ICONDIR、ICONDIRENTRYおよび登録ビットマップ関連データ)を自由に使えるようにしただけのツールです。

 

2.解説-各論

ファイルに沿って説明します。

(1)定数定義

MAX_ICONは、アイコン(カーソル)ファイルの構成要素の最大数を設定しています。これは当初ポインターにして動的に生成しようかな、と思ったのですが、メモリーの確保、解放を繰り返すので(また大したメモリーサイズでもないし)、最初から決め打ち仕様にしました。(まぁ、32個あれば十分でしょうが、もっと欲しい人はここを増やしてください。)

DISPSIZEはアイコン(カーソル)ファイルを読み込む際の画像の大きさの既定値です。128x128と大きくしていますが、好みで変えてください。

 

(2)追加構造体

どういう訳か、ICONDIRとICONDIRENTRYの定義がないので、コンパイルエラーが生じます。その為、ここで定義しています。

 

(3)CICONクラスの宣言

以下コメントの通りですが、その後のメンバー関数の定義も含めて(解説)を入れます。

 

class CICON {  //(解説:派生元はなく、CBMP等と何ら関係ありません。)
public:
    //////メンバー変数//////
    //アイコンを読み込むウィンドウハンドル
    HWND m_hWnd;
    //そのウィンドウのデバイスコンテキストハンドル
    HDC m_hDC;
    //アイコン、カ―ソルのハンドル
    HICON m_hIcon;  //(解説:ビットマップの最大数は32ですが、取り扱うアイコン(カーソル)は「一つ」です。)
    //ICONDIR構造体
    ICONDIR m_Icondir;  //(解説:アイコン(カーソル)ファイルには一つのICONDIRがあり、そのidCountが登録イメージ数になっています。)
    //ICONDIRENTRY構造体
    ICONDIRENTRY m_Iconentry[MAX_ICON];  //(解説:最大数32までのICONDIRENTRY構造体配列です。)
    //構成ビットマップカウンター
    int m_BmpCounter;  //(解説:最大数32までの登録ビットマップを増減できるので、その登録数管理用です。)
    //構成ビットマップのDIB情報取得用(解説:最大数32までの登録ビットマップの各種情報です。サイズが異なるので動的に管理します。)
    LPBYTE  m_ImageDat[MAX_ICON];  //(解説:ビットマップ情報(ヘッダーやパレット)とビットパターンなどの構成ビットマップデータで、ICONDIRENTRYのdwImageOffsetにファイル先頭からのオフセット、dwBytesInResにそのサイズが出ていますので一気に読み込みます。)
    LPBITMAPINFO m_lpBmi[MAX_ICON];    //(解説:m_ImageDatの中のBITMAPINFO構造体へのポインター(結果的にm_ImageDatと同じ)。
    LPBYTE m_XOR[MAX_ICON];            //カラービットマップポインター(解説:m_ImageDatの中のビットパターンを指します。)
    LPBYTE m_AND[MAX_ICON];            //マスクビットマップポインター(解説:m_ImageDatの中のビットパターンを指します。)
    //構成ビットマップのハンドル
    HBITMAP m_hBmp[MAX_ICON];        //カラービットマップ(解説:m_ImageDatのDIBデータでカラービットマップを生成します。)
    HBITMAP m_hMaskBmp[MAX_ICON];    //マスクビットマップ(解説:m_ImageDatのDIBデータでマスクビットマップを生成します。)
    //////メンバー関数//////
    //コンストラクター(1)  //(解説:単なる初期化コンストラクター。)
    CICON();
    //コンストラクター(2)  //(解説:対象ウィンドウ設定迄行うコンストラクター。)
    CICON(HWND);
    //デストラクター
    ~CICON();  //(解説:生成したアイコン、ビットマップ、動的メモリーやデバイスコンテキストを解放します。内容はInit()とClear()です。)
    //CICON初期化
    void Init();  //(解説:アイコンの開放。)
    //すべてのビットマップの廃棄
    void Clear();  //(解説:ビットマップの開放。)
    //ウィンドウを指定する
    void SetWindow(HWND);  //(解説:対象ウィンドウの設定。)
    //構成ビットマップ数を取得する
    int GetCount() {return m_BmpCounter;}  //(解説:見ての通り、m_BmpCounterを返すだけです。)
    //ファイルからアイコンまたはカーソルを取り込む(Icon-1、Cursor-2)
    bool Load_Icon(LPCTSTR, UINT, bool);  //(解説:ファイルを順に読込み、メンバー変数に代入し、ビットマップを生成します。)
    //ファイルからビットマップを追加する
    bool Add_Bitmap(LPCTSTR);  //(解説:これは外部ビットマップファイルを飲み込み、各種メンバー変数の配列の最後に代入します。)
    //登録ビットマップの削除
    bool Del_Bitmap(int);  //(解説:これは特定の登録ビットマップやその関連情報を削除、解放し、空いたところへ後のデータを詰めます。)
    //特定のビットマップのICONDIRENTRYへのポインターを返す
    bool GetIDEntry(int, LPICONDIRENTRY*);  //(解説:データアクセス用。)
    //特定のビットマップのBITMAPINFOHEADERへのポインターを返す
    bool GetBmpInfo(int, PBITMAPINFOHEADER*);  //(解説:データアクセス用。)
    //アイコンのハンドルを返す
    HICON GetIcon();  //(解説:データアクセス用。)
    //カラー(XOR)ビットマップのハンドルを返す
    HBITMAP GetColBmp(int);  //(解説:データアクセス用。)
    //マスク(AND)ビットマップのハンドルを返す
    HBITMAP GetMaskBmp(int);  //(解説:データアクセス用。)
    //ファイルへアイコンまたはカーソルを書き込む(Icon-1、Cursor-2)
    bool Save_Icon(LPTSTR, UINT);  //(解説:本クラスのメンバー変数を順に、、オフセットを計算しながら、書き込むだけです。)
    //HotSpotXを取得する
    int GetHsX(int n) {return m_Iconentry[n].wPlanes;}  //(解説:データアクセス用。)
    //HotSpotYを取得する
    int GetHsY(int n) {return m_Iconentry[n].wBitCount;}  //(解説:データアクセス用。)
    //HotSpotXを設定する
    int SetHsX(int n, int x) {m_Iconentry[n].wPlanes = x;}  //(解説:データアクセス用。)
    //HotSpotYを設定する
    int SetHsY(int n, int y) {m_Iconentry[n].wBitCount = y;}  //(解説:データアクセス用。)
};

 

3.補足-詳論

メンバー関数の定義(実装)に関して補足します。

(1)アイコンファイルの読み込み

既にアイコンファイルを読み込んでいるときにはm_hIconがゼロではないので、それを判断して旧いアイコンの破棄確認をします。なお、構成ビットマップは再利用する場合があるので、残すかどうか選択できます。

 

(2)アイコン、カーソルのロード

前にも書きましたが、LoadImageによるアイコン、カーソル(便宜上アイコンハンドルを使っていますが、何か?汗;)は何番目のビットマップなのか選択できません。従ってCICONクラスのメンバーデータとこの画像データは「ほぼ無関係」とご理解ください。

 

(3)アイコンファイルを読み込む

アイコン(カーソル)ファイルの構造がどうしてこうなっているのかがわかるような処理です。順に読み込んでいくと芋づるですべて埋まります。なお、画像データはサイズ、色数やパレットの有無でサイズが異なるのですべて一挙読みし、後でそれぞれのデータの開始点にポインターを当てています。

 

(4)取得したデータでビットマップを作成

カラービットマップについてはm_ImageDatのDIBデータからビットマップをストレートに生成する典型的なコードです。マスクビットマップについてはカラービットマップのBITMAPINFO(BITMAPINFOHEADERと、ColorBitsが8以下の場合にはRGBQUAD)を改造して生成し、その後元に戻しています。全部読み込んだらビットマップカウンターを加算します。

 

(5)ファイルからビットマップを追加する(Add_Bitmap()関数)

このコードは、EZImageで使うCBMPお発展形、CBMPEXのビットマップ画像をicon/cursorファイルとして保存する関数からとってきました。

CICONにおいてもビットマップファイルをアイコンの登録ビットマップにするためにはBITMAPINFOHEADERからRGBQUADの有無を確認し、ColorBitsに応じたカラービットパターンの分析から「マスクビットマップ(ANDビットマップ)」を作成し、更に16x16サイズの場合にビットの並びを修正することが必要です。

Add_Bitmap(関数の手順は、

①ファイルオープン

②BITMAPFILEHEADERをスキップ

③一旦BITMAPINFOHEADERを読み込む

④カラーパレット(RGBQUAD)サイズを確認

⑤カラービットマップ、マスクビットマップのデータサイズを算出

⑥BITMAPINFOHEADER(40 bytes)、RGBQUADサイズ、カラービットマップ、マスクビットマップのデータサイズを合算して、m_ImageDatのメモリーを確保

⑦ファイルからBITMAPINFOHEADER、カラーパレットとXORビットマップを読み込む

⑧読み込んだXORビットマップパターンの最右下端ピクセルの色を背景色に設定し、色区分毎(上昇・下降型毎)に調査して、マスクビットパターンを作成

⑨幅が16の時には2バイト分の0を入れて修正

⑩カラービットマップ、マスクビットマップを作成

⑪追加が完了したらビットマップカウンターを進める

となっています。

 

(6)登録ビットマップの削除(Del_Bitmap()関数)

単純に引数チェックの上、指定ビットマップと関係データ、メモリーを削除、解放して、次から終わりまでのビットマップデータを詰めてゆき、最後のデータをクリーンにしてからビットマップカウンターを戻します。

 

(7)アイコンファイルの保存(Save_Icon()関数)

CICONデータを順に保存してゆきます。

 

(8)その他

ファイルの取扱は、

①ファイルをCreateFile()関数でオープンして、

②SetFilePointer()関数で読書きする位置へ移動し、

③ReadFile()、WriteFile()関数で読書きし、

④最後にCloseHandle()関数でクローズします。

マスクビットマップデータの作成は、色々調べたのですがWin32APIにそれようの関数がないので、各種色区分のビットマップのビットパターンを調べてビットチェックして作成しています。

 

こんなところでよろしいでしょうか?では今回はここまでで。

さて、今回はBCCSkeltonの<Project名>.hファイル、IconViewer.hを見てゆきます。このファイルの位置づけ、とかはヘルプファイル「SkeltonWizard とBCCSkeltonライブラリー」の「BCCSkeltonによるプログラミングの一般的注意事項」に出ていますのでご覧ください。以下では本プログラム(IconViewer)にparticularな部分について触れます。ソース(IconViewer.h)を参照しながら読んでください。

 

SkeltonWIzardが作成したタイトルとインクルードファイルの下に、

//CICONクラスのヘッダー
#include    "CICON.h"
とあるのは、このプログラムの為に作ったCICONクラスの宣言、定義ファイル(同じディレクトリーにある"CICON.h")を読み込む為にマニュアルで追加しました。このクラスとメンバー等については稿を改めて書きます。

 

次にメインダイアログのクラス定義ですが、SkeltonWizardが作ったコードではCMyWndとなるクラス名を、

class ICONVIEWER : public CDLG
とマニュアルで変更しています。これは後の(DLL化を含む)他プログラムでの転用の為です。

 

「//メニュー項目、ダイアログコントロール関連」は、ダイアログベースですがリストビュー等のコントロール関係はなく、すべてメニュー項目ですね。

 

「//ウィンドウメッセージ関連」では、マクロでビルトインされている

    bool OnInit(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
の他にSkeltonWizardで選択し、自動作成された「ON_DESTROY(IconViewer)」テーブルと「bool OnDestroy(WPARAM, LPARAM);」宣言があります。これに加え、

    //ウィンドウメッセージ関連
    ON_(IconViewer, WM_DROPFILES, OnDropFiles(wParam, lParam))
という見慣れないテーブルと、

    //ウィンドウメッセージ関連
(省略)

    bool OnDropFiles(WPARAM, LPARAM);
という宣言があります。これらは"IconViewerProc.h"のOnInit()における初期化(DragAcceptFiles()関数の実行)、OnDropFiles()関数定義と併せ、BCCSkeltonにおけるドラッグアンドドロップ処理の定番なので、すべてマニュアルで追加しています。将来再利用することがあると思われるので、覚えておくとよいでしょう。(なお、コードの意味を確認されたい方はWM_DROPFILESメッセージやDragQueryFile()、DragFinish()関数をググってみてください。)

 

その他、

    //ユーザー定義関数
    bool SetListView();
    bool ShowIconData(int);
もマニュアルで追加したもので、リストビューの項目やデータ表示、エディットボックスへのデータ表示を行うものです。

 

マクロテーブルの後に続くのは外部変数です。以下説明します。

///////////////////
//ツールバーの作成
///////////////////
CTBAR TBar;
SkeltonWizardが書いた、メインダイアログのツールバーをコントロールするCTBARクラス変数です。予告になりますが、IconViewerProc.hのOnInit()関数定義で、SkeltonWizardが書いたコードで、ツールバーのウィンドウハンドルを与えて初期化し、長々とツールバーボタンの設定をします。
///////////////////////
//ステータスバーの作成
///////////////////////
CSBAR SBar;
同じくSkeltonWizardが書いたメインダイアログのステータスバーをコントロールするCSBARクラス変数です。CTBARと同じくSkeltonWizardがOnInit()関数で初期化コードを書いています。
////////////////////////
//コモンダイアログの作成
////////////////////////
CMNDLG cmndlg;
これもSkeltonWizardが書いたコモンダイアログクラス変数でSkeltonWizardn第1ページの「コモンダイアログ」にチェックを入れるとSkeltonWizardが追加してくれます。「ファイルを開く」ダイアログ、色やフォントの選択ダイアログやフォールダー選択ダイアログを利用できます。
////////////////////////
//CARGインスタンスの作成
////////////////////////
CARG Arg;
ここからはマニュアルで追加した外部変数です。このCARGクラスはWinMain関数では簡単に利用できなくなったC言語の"main(int argc, char** argv)"の郷愁から作ったもので、インスタンスArgを利用してArg.c()で引数の数、Arg.v(n)でn番目の引数文字列のポインターを返したりしますが、その他Arg.Path()やArg.FileName()等色々と利用できる関数があります。
/////////////////
//ヘルプファイル
/////////////////
CSTR g_HelpFile;
BCCSkeltonの文字列クラスCSTRの外部変数で、上記のArgを利用してヘルプファイルのパス、ファイル名を記録しておくものです。

///////////////////////
//ポップアップメニュー
///////////////////////
HMENU g_hPopup;
これはリストビューの右クリックでポップアップメニューを出すためのメニューハンドルです。
///////////////////////
//ファイル起動用フラグ
///////////////////////
bool g_ByFile = FALSE;
これが噂(?)のファイル起動やドラッグアンドドロップで使うフラグです。FALSEで初期化しています。
/////////////////////
//読込ファイル用変数
/////////////////////
CSTR g_FileName;
int g_ImageType = IMAGE_ICON;    //初期値はアイコン
アイコンファイル(*.ico)やカーソルファイル(*.cur)を読み込んだ時に記録する為の変数です。また、いずれのファイルかの区分も記録しておきます。(初期値はアイコンですね。)
///////////////////////
//CICONインスタンス変数
///////////////////////
CICON g_Icon;
これが今回作ったCICONクラスのインスタンスです。別途稿を変えて説明します。
////////////////////////////
//CICONホットスポット用変数
////////////////////////////
int g_Num;    //書き込み処理

カーソルファイルの場合、ICONDIRENTRYのwPlanesとwBitCountにホットスポットが記録されますが、それを書き込む際に利用する変数です。

 

この後に、

class SAVEDLG //「名前をつけて保存」メニュー用のダイアログ

SAVEDLG savedlg;  //そのインスタンス
class HOTSPOTDLG  //ホットスポット入力用ダイアログ

HOTSPOTDLG hotspotdlg;  //そのインスタンス
class VERSIONDLG  //バージョンダイアログ

VERSIONDLG versiondlg;  //そのインスタンス

とメンバー関数の宣言がつづきます。

 

ちょっと駆け足でしたが、基本的にHelloWorldプロジェクトでやったのと同じように、この<Project名>.hにはウィンドウやダイアログの宣言、メンバー変数や関数の宣言、外部変数としてクラスインスタンスやその他のクラスインスタンス変数や外部変数の宣言と初期化、コールバック変数のマクロテーブルが記述されます。(外部変数やユーザー定義関数等が大きくなる場合、メンテナンスを考えて"User.h"を作成する方が良い場合があります。詳しくはヘルプファイルの「BCCSkeltonによるプログラミングの一般的注意事項」を参照してください。)

今回はIconViewer.cppの解説で、「あまり書く事がないな」と思っていたら、プログラムミスを発見してしまいました。ついでに面白い実験もしていますので紹介します。

 

前回「・(EZImage等他のダイアログ、更にDLL化して使えるように、モーダル(注))ダイアログベースのアプリとする」と書きました。

注:老婆心迄。

ご本家MFC:

ちょっと面白い「飼い猫」による説明:

その為、SkeltonWizardが作成したIconViewer.cppファイルはマニュアルで修正を加えています。

 

1. IconViewer.cpp

短いので全文を載せます。

//////////////////////////////////////////
// IconViewer.cpp
//Copyright (c) 04/09/2021 by BCCSkelton
//////////////////////////////////////////
#include    "IconViewer.h"
#include    "IconViewerProc.h"

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

    //ファイルにより起動する場合(g_ByFileフラグを立てる)
    if(Arg.c() > 1)    g_ByFile = TRUE;

    //アクセラレーター登録-InclAccel("(Accelerator ID)")
    IconViewer.InclAccel("IDA_ACCEL");

    //モーダルダイアログとして起動
    IconViewer.DoModal(NULL, "IDD_ICON", IconViewerProc, hInstance);

    return 0;
}


(1)最初の「/ファイルにより起動する場合(g_ByFileフラグを立てる)」

これは「ファイルを投げ入れて起動する」場合(即ち起動コマンドに第二引数-例:"C:\Borland C++\IconViewer\Release\IconViewer.exe C:\Borland C++\SampleIcon\suchandsuch.ico"で起動した場合のファイルパスとデータファイル名(suchandsuch.ico)-がある場合)に(FALSEで初期化されている)グローバル変数g_ByFileをTRUEとしています。(その関連処理はIconViewerProc.hに記述されます。)

 

(2)次の「//アクセラレーター登録-InclAccel("(Accelerator ID)")」

BCCFormで作成されたアクセラレーターがどのようにBCCSkeltonで使われるか、の一般説明をします。

BCCSkeltonではメインとなるウィンドウ(やモードレスダイアログ)に次の形で対応しています。

①ウィンドウ(モードレスダイアログ)を登録(Init関数)

②ウィンドウ(モードレスダイアログ)の作成(Create関数)

③アクセラレーターを登録(InclAccel関数)

④メッセージ処理(Loop関数)

既にBCCSkeltonのInit()とCreate()関数はHelloWorldプロジェクトでやりましたので省略します。

BCCSkeltonのInclAccel関数(③)は単にWin32APIのLoadAcceleratorsを実行するだけです。

BCCSkeltonのLoop関数(④)は以下(例:CDLGクラス)

    while(GetMessage(&m_Msg, m_hWnd, 0, 0)) {
        if(!TranslateAccelerator(m_hWnd, m_Accel, &m_Msg) &&
        !IsDialogMessage(m_hWnd, &m_Msg)) {
            TranslateMessage(&m_Msg);
            DispatchMessage(&m_Msg);
        }
    }
    return m_Msg.wParam;
のようになっており、モードレスダイアログを持つ場合やアクセラレータを使用する場合を前提に書かれています。

実は今回アクセラレーターを作ったのですが、モードレスダイアログでなく、モーダルダイアログに変更した関係上、このLoop関数を使わないのでアクセラレーターも使われません。(驚!)

まぁ、ジョークとしてください。アクセラレーターを使う場合はcppファイルを以下サンプルを参考に元に戻します。

<サンプルモードレスダイアログ>

(略)

    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!IconViewer.Create(NULL, hInstance, "IDD_ICON", IconViewerProc))
        return 0L;
    //アクセラレーター登録-InclAccel("(Accelerator ID)")
    IconViewer.InclAccel("IDA_ACCEL");
    //メッセージループに入る
    return IconViewer.Loop();
(略)

今回の「//モーダルダイアログとして起動」と比べると、モーダルダイアログとモードレスダイアログの違いがよく分かります。(なお、モーダルダイアログとしてビルドするも、プログラムサイズが気になる方はrcファイルのIDA_ACCELとアクセラレーター登録の部分を削除してください。)

 

さて、SkeltonWizardが(HelloWorld.cppのように)モードレスダイアログとしてコーディングしたものを、今回モーダルダイアログにマニュアルで書き換えたのですが、そうする場合、プログラムの終了はEndModal関数で終わらなければなりません。所がプログラミング中にコロッとこれを忘れ、「終了処理がモードレスダイアログのまま(即ちWM_CLOSE-OnClose()とWM_DESTROY-OnDestroy()による終了処理)」になっていることが判明しました。(汗;-しかし実際何ら表面上は正常に作動していたので全く気が付きませんでした。)

 

ではどこでEndModalを使うのでしょうか?

 

(簡単なダイアログのIDOKボタンのように)メニューやツールボタンでモーダルダイアログ処理を終了させる割り込み、即ちIDM_EXITの処理=OnExit()関数の処理(IconViewer.hで確認してください)でしょうか?

 

例えば従来のOnExitでは"SendMsg(WM_CLOSE, 0, 0);"とだけしていましたが、これを削除して"(クラス内定義なのでインスタンス名IconViewer.は不要)EndModal(戻り値-今回の場合なんでも結構です);"とします。

再ビルドして動かすと(当たり前ですが)正常に動作(プログラムの終了)します。し、か、し、「終了確認」ダイアログが現れないことに気が付きます。何故でしょう?

 

そうです、EndModal()(Win32APIのEndDialog()関数)を実行する場合、ダイアログはWM_CLOSEメッセージを出さないのでOnClose()関数を通らないからです。(私も今回初めて知りました。)

 

ではOnExit関数のところにOnClose関数にあった「終了確認」メッセージボックス処理を移し、使わなくなったOnCloseとOnDestroy関数を削除してビルドしましょう。今度は正常に終了確認が求められます。と、こ、ろ、が、ウィンドウ(ダイアログ)の「X」ボタン(これはwParamにSC_CLOSEをいれてWM_SYSCOMMANDメッセージを発生し、受け取ったDefWindowProc() がWM_CLOSEを送ります)を押すと、矢張りストンと終了してしまいます。これではEndModal()関数が実行されません。

 

次にOnDestroy関数を復活させて、EndModal()とWM_SYSCOMMAND+SC_CLOSEを夫々実行させてみると、この関数を通ること(WM_DESTROYメッセージ処理が実行される)ことが分かります。しかし、終了確定のWM_DESTROYで終了確認を求めるのは適切ではないでしょう。

 

ということで、結論的には現在アップされているコードのOnClose関数の定義にEndModal関数を追加することが一番スマートで手間がかからないことが分かります。

【修正コード】

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

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

 

しかし、このブログを書いているおかげで思わぬところでミスを見つけられ、ダイアログの終了プロセスを確認でき、一番有効なソリューションを見つけられたので勉強になりました。

 

では、今回はここまで。(なお、このIconViewerサンプルの修正版は次の修正アップロードの際に載せますので、学習目的のためにも、それまでは↑の【修正コード】をマニュアルで修正してください。)