では、BCCFormandBCCSkelton流儀でまずリソースから入ります。

(1)ダイアログベース、で(3)処理は「ファイルの選択」、「暗号化、復号化処理」、「終了処理」のみ、なので、「ファイルの選択」にはファイル名を表示するテキスト(ES_READONLYにしましょう)とファイルを開くボタン、「暗号化、復号化処理」にはグループボックスで囲った暗号化、復号化のラジオボタンと実行ボタン(WS_GROUP)を暗号化につけて、復号化までできれるように実行ボタンにもWS_GROUPを付けます。

 

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 270, 90
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_MODALFRAME | DS_CENTER | DS_SETFONT
CAPTION "DPAPI"
FONT 8, "MS 明朝"
{
 CONTROL 0, IDC_SELECT, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON | BS_CENTER | BS_VCENTER | BS_ICON, 240, 3, 27, 27
 CONTROL "暗号化", IDC_RBCRYPT, "BUTTON", WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | BS_AUTORADIOBUTTON, 13, 45, 60, 15
 CONTROL "復号化", IDC_RBDECRYPT, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTORADIOBUTTON, 120, 45, 60, 15
 CONTROL "実行", IDC_CRYPT, "BUTTON", WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | BS_PUSHBUTTON, 225, 43, 30, 18
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | BS_PUSHBUTTON, 225, 69, 39, 18
 CONTROL "", IDC_FILE, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_AUTOHSCROLL | ES_LEFT, 3, 9, 234, 13, WS_EX_CLIENTEDGE
 CONTROL "暗号化・復号化選択", IDC_GROUPBOX, "BUTTON", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 3, 36, 261, 30
}
 

//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "C:\Users\(パス)\DPAPI\Icon.ico"

 

なお、ファイルの選択ボタンはテキストではなくICON表示とし、そのアイコンは最初16x16のFILE_OPENの物にしていましたが、せっかくEZImageがあるので、32x32の独自の物を作成しました。

 

IDListを完成させるためには、データの暗号化が必要であり、その為にDPAPI(Data Protection Application Programming Interface)を使うことにします。といっても、高度な暗号化等は難しいので、APIサービスを使った暗号化、復号化関数を使います。

調べてみると、

 

暗号化-CryptProtectData関数

    DPAPI_IMP BOOL CryptProtectData(

        DATA_BLOB *pDataIn,

        LPCWSTR szDataDescr,

        DATA_BLOB *pOptionalEntropy,

        PVOID pvReserved,

        CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,

        DWORD dwFlags,

        DATA_BLOB *pDataOut);

 

復号化-CryptUnProtectData関数

    DPAPI_IMP BOOL CryptUnprotectData(

        DATA_BLOB *pDataIn,

        LPWSTR *ppszDataDescr,

        DATA_BLOB *pOptionalEntropy,

        PVOID pvReserved,

        CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,

        DWORD dwFlags,

        DATA_BLOB *pDataOut );

 

という引数も共通のうってつけな関数があります。

 

最初のpDataInや後で出てくるpOptionalEntropy、pDataOutは、DATA_BLOB構造体へのポインターで、この構造体は、

 

typedef struct _CRYPTOAPI_BLOB {
  DWORD cbData;
  BYTE  *pbData;
} CRYPT_INTEGER_BLOB, *PCRYPT_INTEGER_BLOB, CRYPT_UINT_BLOB, *PCRYPT_UINT_BLOB, CRYPT_OBJID_BLOB, *PCRYPT_OBJID_BLOB, CERT_NAME_BLOB, CERT_RDN_VALUE_BLOB, *PCERT_NAME_BLOB, *PCERT_RDN_VALUE_BLOB, CERT_BLOB, *PCERT_BLOB, CRL_BLOB, *PCRL_BLOB, DATA_BLOB, *PDATA_BLOB, CRYPT_DATA_BLOB, *PCRYPT_DATA_BLOB, CRYPT_HASH_BLOB, *PCRYPT_HASH_BLOB, CRYPT_DIGEST_BLOB, *PCRYPT_DIGEST_BLOB, CRYPT_DER_BLOB, PCRYPT_DER_BLOB, CRYPT_ATTR_BLOB, *PCRYPT_ATTR_BLOB;
 

という、シンプルな割に矢鱈多くの名称を持つ多羅尾坂内的構造体です。しかし内容は簡単で、メンバーのcbDataはデータの長さ、pbDataはデータへのポインターになっているだけです。

 

文字列ポインターのszDataDescrは、矢張りデータと一緒に暗号化される文字列で使わなければNullでよいそうです。

 

DATA_BLOBという構造体のpOptionalEntropyは暗号化に関連する追加的エントロピー、だそうですが、要すれば暗号化パスワードに使うみたいです。いずれにしても使わなければNullでOKです。

 

次のpvReservedもNullにしなければいけません。("must")

 

次のpPromptStructはCRYPTPROTECT_PROMPTSTRUCT構造体で、
    typedef struct _CRYPTPROTECT_PROMPTSTRUCT {
        DWORD   cbSize;        //本構造体のサイズ
        DWORD   dwPromptFlags;   //いつプロンプトを出すか(例:CRYPTPROTECT_PROMPT_ON_PROTECT)
        HWND    hwndApp;       //ダイアログの親ウィンドウのハンドル
        LPCWSTR szPrompt;     //ダイアログのタイトル文字列へのポインター
    } CRYPTPROTECT_PROMPTSTRUCT, *PCRYPTPROTECT_PROMPTSTRUCT;

のように暗号化・復号化を促すプロンプト(専用ダイアログ)の内容を指定する構造体です。

 

次のdwFlagsには「次のいずれか」として、CRYPTPROTECT_LOCAL_MACHINE(ユーザープロフィールではなく、ユーザーが誰であろうと当該ローカルコンピュータのプロファイルで処理を行う)、CRYPTPROTECT_UI_FORBIDDEN(ネットワークでの使用を想定してユーザーインターフェース(UI)を禁止する)等が挙げられていますが、サンプルは'0'となっており、どうも'0'にしておくとユーザープロフィールで処理が行えるようになるようです。

 

最後のDATA_BLOB構造体のpDataOutは処理後データ用です。なお、関数がpbDataにメモリーを割り当てるので、処理後LocalFreeで解放するように書かれています。(When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function.)

 

これならだれでも使えるので、DPAPIプログラムの仕様として、

(1)ダイアログベース

(2)DPAPIの暗号化、復号化関数を使用する

(3)処理は「ファイルの選択」、「暗号化、復号化処理」、「終了処理」のみ

という簡単なものにしましょう。イメージは↓のようにしました。

次回からプログラミング開始です。

 

 

 

 

 

 

 

 

 

 

 

 

 

さて暗号化前のIDListを動かしてみましょう。まずはBCCMakerでビルドします。

 

 

Makeファイルは次のようになっています。

【IDList.mak】

#-----------------------------
# BCCMaker 1.0
# Copyright (C) 2002 by ysama
#-----------------------------
.autodepend
CC="C:\Borland\bcc55\Bin\bcc32.exe"
RC="C:\Borland\bcc55\Bin\brc32.exe"
ASM="C:\nasm-2.14.02\nasm.exe"
CFLAG=-W  -6 -O2 -w- -AT -pc -H- -k -b  
RFLAG=-r
AFLAG=-omf
OUTDIR=-nRelease
CINCS=-I"C:\Borland\BCCForm"
TARGET="C:\Users\(パス)\IDList\Release\IDList.exe"
SRC1="C:\Users\(パス)\IDList\IDList.cpp"
OBJ1="C:\Users\(パス)\IDList\Release\IDList.obj"
RC1="C:\Users\(パス)\IDList\IDList.rc"
RES1="C:\Users\(パス)\IDList\Release\IDList.res"

TARGET: $(TARGET)
$(TARGET): $(OBJ1) $(RES1)
    $(CC) $(CFLAG) -e$(TARGET) $(OBJ1)
    $(RC) $(RESINCS) $(RES1) $(TARGET)
$(OBJ1): $(SRC1)
    $(CC) $(CFLAG) $(CINCS) -o$(OBJ1) -c $(SRC1)
$(RES1): $(RC1)
    $(RC) $(RESINCS) $(RFLAG) -fo$(RES1) $(RC1)

 

ビルドされた実行ファイルを起動して、IDList.exeと同じフォールダーにあるサンプルデータファイル(IDList.dat)を読み込ませ、編集、表示、ヘルプ(「バージョン」...「本ソフトの使い方」は同じフォールダーにIDListHelp.chmファイルが必要です)の動作を確認します。

 

ここで実験です。

このサンプルIDList.datを隠しファイルにします。(注)

注:エクスプローラー(Explorer.exe)でIDList.datを選択、右クリックし、プロパティダイアログを出して、「全般」の「属性」の「隠しファイル」にチェックを入れます。(なお、エクスプローラーは予め「表示」「オプション」のダイアログの「表示」で「ファイルとフォールダーの表示」を確認し、隠しファイル等を表示するようにしておきましょう。)

 

そしてデータを一行増やし、上書きしてみてください。「上書きしますか?」の確認ダイアログの後、「アクセスが拒否されました。」というエラーメッセージが出ます。

 

IDList.datファイルを見てみると、「わんわんクラブ」の行が増えておらず、書き込みに失敗していることが分かります。

次に上記と同じ操作で隠しファイルのチェックを外してみましょう。

 

今度は「既に存在するファイルを作成することはできません。」というエラーメッセージがでますが、IDList.datは次の通り変更(上書き)されます。

【IDList.dat】

Amazon,BCCForm@gmail.com,abc123,"2019年加入"
楽天,BCCForm@gmail.com,xyz789,"2016年加入
もういいかなという感じ"
アメーバーブログ,BCCForm@gmail.com,lmn456,"2021年から"
わんわんクラブ,BCCForm@gmail.com,abc123,"ダミーだよ"

 

解説すると、前回のブログ

 

    //ファイルの書き込み
    g_Data.ToFile(cp);

(解説:この一行だけで、cpが指すファイル名でコンマ区切りファイルを書き込みます。)
    ErrorMsg(m_hWnd);
(解説:前回ErrorMsg()関数を紹介したので無理やりこれをここに挿入してみました。なお、IDListhファイルの#include文の後にこの関数を入れたError.hファイルを追加するために「#include "Error.h"」を入れること、最後はその#include文とこの行を削除することを忘れないで下さい。この関数を使って実験をしてみる予定です。)

 

と書きました。ErrorMsgダイアログのメッセージがGetLastErrorの示すメッセージであり、最初のエラーはIDListの書き込みの際、「隠しファイル」の場合、「読み取り専用ファイル」と同様にアクセスができずエラー(処理の中断)となります(注)が、隠しファイル属性を取るときちんと処理を行う一方、(上書きなので)既存のファイルを新規に作成はできない、というエラーメッセージも発していることが分かります。

注:"If CREATE_ALWAYS and FILE_ATTRIBUTE_NORMAL are specified, CreateFile fails and sets the last error to ERROR_ACCESS_DENIED if the file exists and has the FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM attribute."

 

IDListでは隠しファイルにしたデータファイルに対して自動読み込みと書き込み処理を行う予定なので、予めErrorMsg関数を使って予備知識として紹介しておきました。

 

さて、IDListの完成の前に、次回からはWin32が提供するDPPI(Data Protect Application Program Interface)の関数を使って、ファイルを暗号化、復号化するツールを作ってみましょう。プログラム名はストレートに「DPAPI」とします。

 

それではIDListProc.hの残りの部分、メニューとダイアログの関連関数について解説してゆきましょう。

 

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

    //データファイルの読み込み
    char* cp = cmndlg.GetFileName(m_hWnd, "datファイル(*.dat)\0*.dat\0全てのファイル(*.*)\0*.*\0\0", TRUE, "dat", ".");

(解説:このCMNDLGクラスで、ファイル、色、フォントやファイルパスのダイアログによる選択ができます。cpにはキャンセルされた場合Null(0)、選択された場合ファイルパス、名のポインターが入ります。)
    if(cp)
        g_Data.FromFile(cp);

(解説:ものすごく簡単に書いてますが、CSTRクラス変数g_Dataにcpが指す(テキスト)ファイルを読み込ませました。)
    else {
        MessageBox(m_hWnd, "ファイルが選択されていません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
     //データ読み出し-行読み出しを繰り返す
    int i = 0;
    while(cp = GetDelimtStr(g_Data.ToChar())) {
        //一行から語句を読み出し、コラムにセットすることを4回繰り返す
        g_ListView.InsertItem(i, cp, -1, i);    //LVIF_PARAMをソートの為に追加
        cp = GetDelimtStr(g_Data.ToChar());
        g_ListView.SetItem(i, 1, cp);
        cp = GetDelimtStr(g_Data.ToChar());
        g_ListView.SetItem(i, 2, cp);
        cp = GetDelimtStr(g_Data.ToChar());
        g_ListView.SetItem(i, 3, cp);
        i++;
    }

(解説:ここでやっているのは、iを行数とし、0行からUser.hの「一行から4列分の文字列を切り出す」GetDelimitStr関数を使ってg_Dataに読み込ませたコンマ区切りファイル(ToChar()関数はCSTRクラスのデータをchar*で返します)を切り出し、終端が来るまで繰り返しリストビューに表示する処理です。)
    return TRUE;
}

bool CMyWnd::OnSave() {

    //ファイルの保存場所を指定する
    char* cp = cmndlg.GetFileName(m_hWnd, "datファイル(*.dat)\0*.dat\00",
                        FALSE, "dat", ".");
    if(!cp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }

(解説:これもcpにファイル名を取得します。ファイルを選ぶダイアログがキャンセルされれば処理を中断します。)
    //ファイルの保存
    g_Data = "";    //    初期化
     //行数だけ文字列化を繰り返す
    int n = g_ListView.GetItemCount();
    int i, j;
    for(i = 0; i < n; i++) {
        char buff[MAX_PATH];
        CSTR row = "";
        //g_ListViewの4列のデータを読み出し、コンマ区切の文字列一行とする
        for(j = 0; j < 3; j++) {
            g_ListView.GetItemText(i, j, buff, MAX_PATH);
            row = row + buff;
            row = row + ",";
        }
        g_ListView.GetItemText(i, j, buff, MAX_PATH);
        row = row + "\"";    //最終項目は
        row = row + buff;    //文字列中に'\n'があるので
        row = row + "\"";    //「""」で囲う
         //一行データをg_Dataへ足しこんでゆく
        g_Data = g_Data + row;
        g_Data = g_Data + "\n";
    }

(解説:これは読込とは逆にリストビューのデータを0行から(行数-1)行まで文字列としてコンマ区切りで収納し、行末にCR(\n)を入れます。第4列は複数行を許すために「""(ダブルクォテーション)」でくくります。)
    //ファイルの書き込み
    g_Data.ToFile(cp);

(解説:この一行だけで、cpが指すファイル名でコンマ区切りファイルを書き込みます。)
    ErrorMsg(m_hWnd);
(解説:前回ErrorMsg()関数を紹介したので無理やりこれをここに挿入してみました。なお、IDListhファイルの#include文の後にこの関数を入れたError.hファイルを追加するために「#include "Error.h"」を入れること、最後はその#include文とこの行を削除することを忘れないで下さい。この関数を使って実験をしてみる予定です。)

    return TRUE;
}

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);

(解説:この関数はSendMessage(m_hWnd, ...)と等価です。)
    return TRUE;
}

bool CMyWnd::OnAdd() {

    //追加の場合は、データ受け渡し用のg_Data(CSTR)を空(NULL)にする
    g_Data = "";

(解説:メインウィンドウとダイアログ間のデータ受け渡しにg_Dataを使います。関数の引数で渡すとスマートなんですが、外部変数で渡すこととしました。)
    //編集用ダイアログを追加用ダイアログとして使う
    if(!editdlg.DoModal(m_hWnd, "IDD_EDIT", editdlgProc) ||
        !*g_Data.ToChar())                //キャンセルされた場合、または
        return FALSE;                    //データが空の場合
(解説:コメント通りです。ダイアログがキャンセルされたか、g_Dataが空の場合、FLASE(0)を返すようにします。データ受け渡し用CSTRクラス変数g_Dataのデータをchar*で返すToChar()関数を使い、最初の文字(*g_Data.ToChar())をチェックしています。)

    int n = g_ListView.GetItemCount();    //リストの最後尾を確認

(解説:nには行数が入ります。x行の場合は第0行から第(x - 1)行となりますので、第x行は次の新しい行を意味します。)
    //CSTR g_Dataのカンマ区切り文字列をEdit1~4までに格納
    char *cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.InsertItem(n, cp, -1, n);    //LVIF_PARAMをソートの為に追加
    SBar.SetText(1, cp);                //ステータスバーにメンバーシップ表示
    cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItem(n, 1, cp);
    cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItem(n, 2, cp);
    cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItem(n, 3, cp);
(解説:ファイルの読み込みで見た処理です。4列分のデータをGetDelimitStr関数を使って切り出し、リストビューにセットします。)

    return TRUE;
}

bool CMyWnd::OnSearch() {

    static CSTR ToFind;                    //検索文字列保存用変数
    static int found = 0;                //初期値は最初から
    char buff[MAX_PATH];                //リストボックス文字列取得用バッファ
    int n = g_ListView.GetItemCount();    //リストボックスの行数確認

(解説:以上は検索処理の準備です。内容はコメント通りです。)
    //検索文字列取得ダイアログ
    SBar.SetText(1, "検索する項目に含まれる文字列を入力してください");

(解説:ステータスバーを使ってユーザーに指示を出します。)
    searchdlg.DoModal(m_hWnd, "IDD_SEARCH", searchdlgProc);
(解説:検索用のsearchdlgダイアログから検索文字列をg_Dataに入れて受け取ります。)

    //前回の検索と同じ検索文字列であれば検索を継続
    if(g_Data != ToFind) {    //異なれば
        ToFind = g_Data;        //検索文字列を更新し
        found = 0;            //最初から検索する
        g_ListView.SetItemState(-1, 0, LVIS_SELECTED);    //全行の選択状態の解除
    }

(解説:前回の検索文字列(初回の場合はNull)とダイアログからとってきたg_Dataの検索文字列が異なれば、検索処理の初期化を行います。) 
    for(int i = found; i < n; i++) {        //行
        for(int j = 0; j < 4; j++) {        //列
            g_ListView.GetItemText(i, j, buff, MAX_PATH);    //文字列取得
            if(strstr(buff, ToFind.ToChar())) {            //検索文字列が含まれれば
                found = i + 1;                //次の検索のために、見つかった行の次を指す
                SetFocus(g_ListView.m_hWnd);    //g_ListViewにフォーカスがないと選択状態が表示されない
                g_ListView.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);    //選択状態にする
                return TRUE;
            }
        }
    }
    MessageBox(m_hWnd, "検索文字は見当たりませんでした", "検索結果", MB_OK | MB_ICONINFORMATION);
    g_ListView.SetItemState(-1, 0, LVIS_SELECTED);    //全行の選択状態の解除
    return TRUE;
(解説:あとは第0行の第0列のセルから横へ検索開始し、なければ次の行に移ります。見つかった時は選択状態をそのままにしてフォーカスを当てて表示します。その状態で再度同じ文字列を検索する場合には次の行(found + 1)から再開します。)

}


bool CMyWnd::OnEdit() {

    int n = g_ListView.GetNextItem(-1, LVNI_ALL | LVNI_SELECTED);
(解説:選択行を調べます。)

    if (n == -1) {
        MessageBox(m_hWnd, "項目が選択されていません", "警告", MB_OK);
        return FALSE;
    }

(解説:選択されていなければ処理を中断します。)
    //CSTR g_Dataに4列の項目データをカンマ区切りで格納
    char buff[MAX_PATH];
    g_ListView.GetItemText(n, 0, buff, MAX_PATH);
    g_Data = buff;
    g_Data = g_Data + ",";
    g_ListView.GetItemText(n, 1, buff, MAX_PATH);
    g_Data = g_Data + buff;
    g_Data = g_Data + ",";
    g_ListView.GetItemText(n, 2, buff, MAX_PATH);
    g_Data = g_Data + buff;
    g_Data = g_Data + ",";
    g_ListView.GetItemText(n, 3, buff, MAX_PATH);
    g_Data = g_Data + "\"";    //最終項目は
    g_Data = g_Data + buff;    //文字列中に'\n'があるので
    g_Data = g_Data + "\"";    //「""」で囲う

(解説:これは選択行のデータをg_Data変数にカンマ区切りで格納する処理です。)
    //編集用ダイアログを呼び出す
    if(!editdlg.DoModal(m_hWnd, "IDD_EDIT", editdlgProc))
        return FALSE;;
(解説:編集用のeditdlgダイアログがキャンセルされるとFALSE(0)を返すので処理を中断します。)

    //CSTR g_Dataに格納されたカンマ区切りデータを4列の項目に格納
    char *cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItemText(n, 0, cp);
    SBar.SetText(1, cp);
    cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItemText(n, 1, cp);
    cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItemText(n, 2, cp);
    cp = GetDelimtStr(g_Data.ToChar());
    g_ListView.SetItemText(n, 3, cp);
(解説:これはg_Data変数を使って編集ダイアログで編集されたカンマ区切りデータを受け取り、それをリストビューに表示する処理です。第1列はステータスバーにも表示されます。)
    return TRUE;
}

bool CMyWnd::OnDelete() {

    //複数選択に対応
    int n = g_ListView.GetNextItem(-1, LVNI_ALL | LVIS_SELECTED);
    while(n != -1) {
        g_ListView.DeleteItem(n); 
        n = g_ListView.GetNextItem(n - 1, LVNI_ALL | LVIS_SELECTED);
    }

(解説:選択された行がないとGetNextItem関数は-1を返すので、それまで行削除を繰り返します。)
    SBar.SetText(1, "選択メンバーシップ");    //ステータスバー表示変更
    return TRUE;
}

bool CMyWnd::OnSort() {

    sortdlg.DoModal(m_hWnd, "IDD_SORT", sortdlgProc);
(解説:sortdlgダイアログを呼び出します。処理はダイアログで行います。)

    return TRUE;
}

bool CMyWnd::OnHelp() {

    WinExec(g_HelpFile.ToChar(), SW_SHOWNORMAL);    //g_HelpFileがコマンドライン
(解説:これがOnCreate()で用意したg_HelpFile("hh \"(ヘルプファイルパス、名)\"")というコマンド文字列の実行部分です。)

    return TRUE;
}

bool CMyWnd::OnVersion() {

    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc);

(解説:バージョンダイアログを表示するだけです。)
    return TRUE;
}

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool EDITDLG::OnInit(WPARAM wParam, LPARAM lParam) {
(解説:編集ダイアログのWM_INITDLGメッセージ処理です。)

    if(!*g_Data.ToChar()) {
        SendMsg(WM_SETTEXT, 0, (LPARAM)"IDとパスワードの追加");
        SendItemMsg(IDOK, WM_SETTEXT, 0, (LPARAM)"登録終了");
        return TRUE;
    }

(解説:編集と追加を同じダイアログで行うという手抜きをしたので、データ受け渡し用g_Data変数が空なら、追加ダイアログとしてタイトルとIDOKボタンのテキストを変更しています。なお、以降は編集ダイアログの処理です。)
    //GetDelimitStrはソースを短縮してしまうのでg_Dataは保護する
    CSTR Data = g_Data;
    //CSTR g_Dataのカンマ区切り文字列をEdit1~4までに格納
    char *cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT1, WM_SETTEXT, NULL, (LPARAM)cp); 
    cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT2, WM_SETTEXT, NULL, (LPARAM)cp); 
    cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT3, WM_SETTEXT, NULL, (LPARAM)cp); 
    cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT4, WM_SETTEXT, NULL, (LPARAM)cp); 
(解説:g_Dataからのデータはキャンセルで戻った時にそのまま使うので、一旦ローカル変数にコピーし、そのローカル変数をGetDelimitStr関数で切り出し、所定のエディットボックスに格納してゆきます。)

    return TRUE;
}

bool EDITDLG::OnIdok() {

    //CSTR g_Dataにカンマ区切りでEdit1~4までの文字列を格納
    char buff[MAX_PATH];
    SendItemMsg(IDC_EDIT1, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM)buff); 
    g_Data = buff;
    g_Data = g_Data + ",";
    SendItemMsg(IDC_EDIT2, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM)buff); 
    g_Data = g_Data + buff;
    g_Data = g_Data + ",";
    SendItemMsg(IDC_EDIT3, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM)buff); 
    g_Data = g_Data + buff;
    g_Data = g_Data + ",";
    SendItemMsg(IDC_EDIT4, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM)buff); 
    g_Data = g_Data + "\"";    //最終項目は
    g_Data = g_Data + buff;    //文字列中に'\n'があるので
    g_Data = g_Data + "\"";    //「""」で囲う

    EndModal(TRUE);

(解説:処理終了ボタンが押されたら、各エディットボックスの内容をbuff配列に読込み、g_Dataにカンマ区切りで追加してゆきます。最終項目(第4列の備考)は括弧で囲みます。)
    return TRUE;
}

bool EDITDLG::OnIdcancel() {

    EndModal(FALSE);
(解説:これがコントロールにはないIDCANCEL定数の処理です。EndModal関数でFALSE(0)を返します。)

    return TRUE;
}

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

    SendItemMsg(IDC_EDIT1, WM_SETTEXT, 0, (LPARAM)m_ToFind);
(解説:既に検索歴があればメンバー変数に保存した検索文字列をエディットボックスに表示します。)

    return TRUE;
}

bool SEARCHDLG::OnIdok() {

    SendItemMsg(IDC_EDIT1, WM_GETTEXT, MAX_PATH, (LPARAM)m_ToFind);
    g_Data = m_ToFind;
    EndModal(TRUE);
(解説:エディットボックスの検索文字列はメンバー変数に読込み、g_Dataを介してメインウィンドウ処理に受け渡します。)

    return TRUE;
}

bool SEARCHDLG::OnIdcancel() {

    EndModal(FALSE);

(解説:EndModal関数でFALSE(0)を返すIDCANCEL定数の処理です。)

   return TRUE;
}

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

    //コンボボックスに文字列を登録
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"メンバーシップ");
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"ログインID");
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"パスワード");
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"備考");
    SendItemMsg(IDC_COMBOBOX1, CB_SETCURSEL, 0, 0);    //一番目を選択状態にする
    SendItemMsg(IDC_COMBOBOX2, CB_ADDSTRING, 0, (LPARAM)"昇順");
    SendItemMsg(IDC_COMBOBOX2, CB_ADDSTRING, 0, (LPARAM)"降順");
    SendItemMsg(IDC_COMBOBOX2, CB_SETCURSEL, 0, 0);    //一番目を選択状態にする
(解説:コメントに書いてある通りの初期設定を行います。)

    return TRUE;
}

bool SORTDLG::OnIdok() {

    //選択番号の取得
    int col = SendItemMsg(IDC_COMBOBOX1, CB_GETCURSEL, 0, 0);
    int dir = SendItemMsg(IDC_COMBOBOX2, CB_GETCURSEL, 0, 0);
(解説:col変数に列番号、dir変数にUPまたはDOWNを読み込みます。)

    //ソート対象項目に昇順、降順のデータを書込む
    sortsubno[col] = dir;
    //ソート処理
    g_ListView.SortItem(CompProc, col);

(解説:ソート処理でデータ受け渡しに使うsortsubno配列にデータをセットし、SortItem関数でソートします。)
    EndModal(TRUE);
    return TRUE;
}

bool SORTDLG::OnIdcancel() {

    EndModal(FALSE);

(解説:EndModal関数でFALSE(0)を返すIDCANCEL定数の処理です。)

    return TRUE;
}

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

    //スタティックコントロールに文字列を表示
    SendItemMsg(IDC_VERTXT, WM_SETTEXT,
                    0, (LPARAM)"IDList\nVersion 1.0 by YSAMA");

(解説:バージョンダイアログのテキストをスタティックコントロールにセットする処理です。)

    return TRUE;
}

bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);

(解説:TRUEでもFALSEでも処理は変わらないのでIDCANCEL定数の処理はしません。)

    return TRUE;
}

 

以上でデータの暗号化なしカンマ久区切りテキストのデータの読み書きができるIDList.exeが出来上がりました。

因みにテストデータを保存したのが次のテキストです。基本カンマ区切り4項目で一行ですが、ダブルクォテーション("")内にコントロール記号があれば複数行になります。

 

【サンプルIDList.dat】

Amazon,BCCForm@gmail.com,abc123,"2019年加入"
楽天,BCCForm@gmail.com,xyz789,"2016年加入
もういいかなという感じ"
アメーバーブログ,BCCForm@gmail.com,lmn456,"2021年から"
 

次回はこのファイルを使ってErrorMsgj関数のテストを行いましょう。

IDListはちょっとお休みして、プログラミング上ちょっといいツールを紹介しましょう。

Win32の関数やCOMなどを使うと、エラーが出たときにその内容を教えてくれるGetLastError関数があります。しかし戻り値(last-error code value)が整数なので人間にはわかりません。

一方、この整数コードを人間の理解できる文章(テキスト)化するFormatMessage関数もあります。

俺らを組み合わせると、エラーが出た際にそこにトラップ(スタブ)を仕掛けることができ、何が起こったのが確認できます。

 

///////////////////////////////////////////
// ErrorMsg (Error Message) 関数
// 使用方法:プログラム冒頭に定義した後、
// プログラムのエラー箇所に入れて確認する。
///////////////////////////////////////////
void ErrorMsg(HWND hWnd){
    
    DWORD err_code = GetLastError();
    LPVOID lpMsg;                        //メッセージ用テキストバッファポインタ
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER    //メッセージ用のメモリ要求
        | FORMAT_MESSAGE_FROM_SYSTEM        //Windowsのエラーメッセージを検索
        | FORMAT_MESSAGE_IGNORE_INSERTS,        //メッセージの挿入、最後引数を無視
        NULL, err_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),    //無視、エラーコード、言語指定
        (LPTSTR)&lpMsg, 0, NULL);            //バッファへポインタ、不要、無視(挿入数値)        
    MessageBox(hWnd, (LPTSTR)lpMsg, "エラー", MB_OK | MB_ICONEXCLAMATION);
    LocalFree(lpMsg);                    //メッセージテキスト用メモリーの解放
}
 

この関数を入れたファイルをError.hとでもしておいて、必要に応じてincludeし、エラーを出す処理の直後に"例:ErrorMsg(m_hWnd);"などと挿入して使います。

 

今回はIDListの動作を支えるIDListProc.hのウィンドウメッセージ部分を、いつも通り(解説:)と色分けで解説します。

 

//////////////////////////////////////////
// IDListProc.h
// Copyright (c) 09/29/2021 by BCCSkelton
//////////////////////////////////////////

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

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

    //ツールバー登録-Init(hWnd, hIinstance, ID, Style)
    TBar.Init(m_hWnd, m_hInstance, TOOLBAR, WS_CHILD | WS_VISIBLE | TBSTYLE_TOOLTIPS);
    //ツールバーボタン用カスタムビットマップ追加
    TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 10);
    //ツールバーボタン追加
    TBBUTTON tbb[14];
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = TBar.m_id;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_OPEN;
    tbb[1].iBitmap = TBar.m_id + 1;
    tbb[1].fsState = TBSTATE_ENABLED;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = IDM_SAVE;
    tbb[2].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[3].iBitmap = TBar.m_id + 2;
    tbb[3].fsState = TBSTATE_ENABLED;
    tbb[3].fsStyle = TBSTYLE_BUTTON;
    tbb[3].idCommand = IDM_EXIT;
    tbb[4].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[5].iBitmap = TBar.m_id + 3;
    tbb[5].fsState = TBSTATE_ENABLED;
    tbb[5].fsStyle = TBSTYLE_BUTTON;
    tbb[5].idCommand = IDM_ADD;
    tbb[6].iBitmap = TBar.m_id + 4;
    tbb[6].fsState = TBSTATE_ENABLED;
    tbb[6].fsStyle = TBSTYLE_BUTTON;
    tbb[6].idCommand = IDM_SEARCH;
    tbb[7].iBitmap = TBar.m_id + 5;
    tbb[7].fsState = TBSTATE_ENABLED;
    tbb[7].fsStyle = TBSTYLE_BUTTON;
    tbb[7].idCommand = IDM_EDIT;
    tbb[8].iBitmap = TBar.m_id + 6;
    tbb[8].fsState = TBSTATE_ENABLED;
    tbb[8].fsStyle = TBSTYLE_BUTTON;
    tbb[8].idCommand = IDM_DELETE;
    tbb[9].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[10].iBitmap = TBar.m_id + 7;
    tbb[10].fsState = TBSTATE_ENABLED;
    tbb[10].fsStyle = TBSTYLE_BUTTON;
    tbb[10].idCommand = IDM_SORT;
    tbb[11].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[12].iBitmap = TBar.m_id + 8;
    tbb[12].fsState = TBSTATE_ENABLED;
    tbb[12].fsStyle = TBSTYLE_BUTTON;
    tbb[12].idCommand = IDM_HELP;
    tbb[13].iBitmap = TBar.m_id + 9;
    tbb[13].fsState = TBSTATE_ENABLED;
    tbb[13].fsStyle = TBSTYLE_BUTTON;
    tbb[13].idCommand = IDM_VERSION;
    TBar.AddButtons(14, tbb);

    //ステータスバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    SBar.Init(m_hWnd, m_hInstance, STATUSBAR);
    //ステータスバー区画設定
    int sec[2] = {100, 400};
    SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "IDList Version 1.0");
    SBar.SetText(1, "選択メンバーシップ");
//(解説:ここまではほとんどSkeltonWizard通りです。ステータスバーの第2区分は選択メンバーシップを表示するつもりですので、初期値にそれが分かるように入れました。)


    //リストビューコントロールの作成
    //複数選択を許さない場合、スタイルにLVS_SINGLESELを加える
    g_ListView.Create(m_hWnd, m_hInstance, IDC_LISTVIEW);
    g_ListView.InsertColumn(0, 240, "メンバーシップ");
    g_ListView.InsertColumn(1, 160, "ログインID", LVCFMT_CENTER);
    g_ListView.InsertColumn(2, 160, "パスワード", LVCFMT_CENTER);
    g_ListView.InsertColumn(3, 240, "備考");
//(解説:CLISTVIEWクラスのインスタンス(要すればリストビューの実体)を、メインウィンドウのハンドル(常にm_hWnd)、インスタンスハンドル(m_hInstance)とリストビューコントロールのIDを与えて実装します。

//次にまっさら白地のリストビューを第1列から第4列まで幅とタイトルを与えて4列にします。)

    //メニューハンドルの取得
    g_hPopup = GetSubMenu(GetMenu(m_hWnd), 1);
//(解説:User.hで宣言したポップアップメニュー用ハンドル変数に「編集」メニューのポップアップメニューのハンドルを入れます。)
    //リストビューは項目未選択なので「編集」と「削除」を無効にする
    EnableMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), IDM_EDIT, MF_GRAYED | MF_BYCOMMAND);
    EnableMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), IDM_DELETE, MF_GRAYED | MF_BYCOMMAND);
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_EDIT, FALSE);
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_DELETE, FALSE);
//(解説:コメント通りメニュー項目を無効化しています。)
    //CHMヘルプファイルの準備
    CARG arg;
    g_HelpFile = "hh \"";
    g_HelpFile = g_HelpFile + arg.Path();
    g_HelpFile = g_HelpFile + "\\IDListHelp.chm";
//(解説:ここでローカス変数としてCARGクラスが出てきます。これは当該プログラムのファイルパスやファイル名、引数を取得するユーティリティ的クラスです。(例えば、arg.c()が引数の個数、arg.v()がその文字列ポインター配列を取得できる、C言語のオマージュとなっています。)

//BCCSkeltonの文字列クラスCSTRの外部変数g_HelpFileにMicrosoftのHelpWorkShopのコマンド("hh")を入れ、その引数としてファイルパスと"IDListHelp.chm"を追加することでこの変数をWinExecに渡せばヘルプファイルが立ち上がることになります。)

     return TRUE;

}

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

//(解説:以下はリストビューからの通知をまとめた部分です。)

    if((int)wParam == IDC_LISTVIEW) {    //リストビューからのメッセージ
        int n = g_ListView.GetNextItem(-1, LVNI_ALL | LVIS_SELECTED);
        if(n == -1) {    //未選択なら
            //「編集」と「削除」を無効にする
            EnableMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), IDM_EDIT, MF_GRAYED | MF_BYCOMMAND);
            EnableMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), IDM_DELETE, MF_GRAYED | MF_BYCOMMAND);
            SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_EDIT, FALSE);
            SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_DELETE, FALSE);
        }
        else {
            //「編集」と「削除」を有効にする
            EnableMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), IDM_EDIT, MF_UNCHECKED | MF_BYCOMMAND);
            EnableMenuItem(GetSubMenu(GetMenu(m_hWnd), 1), IDM_DELETE, MF_UNCHECKED | MF_BYCOMMAND);
            SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_EDIT, TRUE);
            SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_DELETE, TRUE);
            //選択メンバーシップステータスバーに表示
            char buff[MAX_PATH];
            g_ListView.GetItemText(n, 0, buff, MAX_PATH);
            SBar.SetText(1, buff);
        }
//(解説:リストビューの行が選択されていれば「編集」と「削除」のメニューとツールバーボタンが有効となり、行の削除等で選択が解除されるとまた無効になります。)

        //リストビューの通知メッセージによる処理
        switch(((LPNMLISTVIEW)lParam)->hdr.code) {
        case LVN_COLUMNCLICK:    //列見出しがクリックされたら
            if(sortsubno[((LPNMLISTVIEW)lParam)->iSubItem] == UP)
                sortsubno[((LPNMLISTVIEW)lParam)->iSubItem] = DOWN;
            else
                sortsubno[((NM_LISTVIEW*)lParam)->iSubItem] = UP;
            g_ListView.SortItem(CompProc, ((LPNMLISTVIEW)lParam)->iSubItem);
            break;

//(解説:これがリストビューの列枠をクリックした際の処理で、lParamがLPNMLISTVIEW構造体のポインターとなっており、そのメンバーであるiSubItemで与えられるクリックされた列番号に対応するsortsubno配列変数にUP(0)もしくはDOWN(1)を入れておくと、User.hにあるソートのコールバック関数へ飛んでソートすることになります。)
        case NM_DBLCLK:        //左ダブルクリックしたら
            if(g_ListView.GetNextItem(-1, LVNI_ALL | LVNI_SELECTED) == -1)
                OnAdd();
            else
                OnEdit();
            break;
//(解説:これはリストビューがダブルクリックされた際、行が未選択なら「追加」、選択状態なら「編集」の関数を呼び出す処理です。)

        case 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);
            break;
        }
//(解説:これは右クリックでのポップアップメニュー処理です。定番ですので覚えるとよいでしょう。)

        return TRUE;
    }
    //ツールバーツールチップ-因みにこの時のidCtrl(wParam)はメニューアイテムになる(例:IDM_OPEN)
    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {    //TTN_GETDISPINFOと同じ
        ((LPTOOLTIPTEXT)lParam)->hinst = m_hInstance;
        char* tag;
        switch(((LPTOOLTIPTEXT)lParam)->hdr.idFrom) {
        case IDM_OPEN:
            tag = "データを開く";
            break;
        case IDM_SAVE:
            tag = "データの保存";
            break;
        case IDM_EXIT:
            tag = "終了";
            break;
        case IDM_ADD:
            tag = "追加";
            break;
        case IDM_SEARCH:
            tag = "検索";
            break;
        case IDM_EDIT:
            tag = "編集";
            break;
        case IDM_DELETE:
            tag = "削除";
            break;
        case IDM_SORT:
            tag = "並び替え";
            break;
        case IDM_HELP:
            tag = "本ソフトの使い方";
            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();
//(解説:これはSkeltonWizardが書いてくれます。)

    //最後の「備考」幅を調整してg_ListViewをクライアント領域に合わせる
    RECT rec;
    GetClientRect(m_hWnd, &rec);
    g_ListView.m_x = rec.left;
    g_ListView.m_Width = rec.right - rec.left;
    g_ListView.m_Height = rec.bottom - rec.top;
    GetWindowRect(TBar.GetHandle(), &rec);
    g_ListView.m_y = rec.bottom - rec.top;
    g_ListView.m_Height -= g_ListView.m_y;
    GetWindowRect(SBar.GetHandle(), &rec);
    g_ListView.m_Height -= rec.bottom - rec.top;
    g_ListView.Move(g_ListView.m_x, g_ListView.m_y, g_ListView.m_Width, g_ListView.m_Height);
    g_ListView.SetColumn(3, LVCFMT_LEFT, g_ListView.m_Width - 560, "備考");
    return TRUE;
//(解説:これはメインウィンドウのサイズが変更された場合、クライアント領域からツールバーとステータスバーを除外した領域にリストビューをすっぽりと合わせてサイズ調整する処理(リストビューのメンバー変数m_x、m_Width、m_y、m_Heightを調整してMoveさせる)です。他のコントロールとちょっと違うのは、幅調整を第4列の「備考」で行うことです。なお"560"というのはOnCreate()関数で設定した「メンバーシップ」から「パスワード」までの3列の幅の合計です。)

}

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;
}

//(解説:ここもSkeltonWizardが書いた通りです。終了時処理があればここをいじります。)

 

今回はUser.hで定義する外部変数や関数を紹介しますが、その詳しい意味はProc.hの解説の際に書きますので、今日は

(1)IDList.hやIDListProc.hでカバーされないことをUser.hに書く

(2)IDLいstのUser.hの外部変数や関数「大体こんなこと」

位の理解で結構です。

なお、今回も(解説:)と色付けでやりますね。

 

//////////////////////////////////////////
// User.h
// Copyright (c) 09/29/2021 by Ysama
//////////////////////////////////////////
//定数の定義
#define    IDC_LISTVIEW 1000
//(解説:これはメインウィンドウに張り付けるリストビューのコントロールIDです)

#define UP 0
#define DOWN 1
//(解説:これはリストビューの列枠をクリックして昇順、降順にソートする際の定数です)

#define NO_OF_SUBITEM 4
//(解説:これも昇順、降順にソートする際の列数の定数です)

 

////////////////////////
//リストビューの作成
////////////////////////
CLISTVIEW g_ListView;
//(解説:リストビューコントロールをカプセル化するCLISTVIEWクラスの変数定義です)
//////////////////////////
//データファイル用CSTR変数
//////////////////////////
CSTR g_Data;
//(解説:IDListのデータの受け渡しに利用する、BCCFormandBCCSkeltonの文字列クラス変数です)
//////////////////////////
//ヘルプファイル用CSTR変数
//////////////////////////
CSTR g_HelpFile;
//(解説:IDList.chmというヘルプファイルを作る想定ですが、そのヘルプファイルのパス、名の文字列変数です)
////////////////////////////////
//ポップアップメニュ-のハンドル
////////////////////////////////
HMENU g_hPopup;
//(解説:リストビューを右クリックするとポップアップメニューを出す予定なので、そのハンドル変数を確保します)
//////////////////////////
//サブアイテムのソート関連

//(解説:サブアイテム=リストビューの列です)
//////////////////////////
//列とソート方法の配列
int sortsubno[NO_OF_SUBITEM];
//(解説:この配列が4つの列に対応し、それぞれUP(0)またはDOWN(1)のフラグを収容します)
//ソートコールバック関数
//(解説:リストビューの列枠をクリックして昇順、降順にソートする際に必要なコールバック関数です)
int CALLBACK CompProc(LPARAM lp1, LPARAM lp2, LPARAM lpSort) {

    static int nItem1, nItem2;
    static char buf1[MAX_PATH], buf2[MAX_PATH];
//(解説:ソートに必要な整数、文字列の変数宣言です)
    //アイテムの検索
    nItem1 = g_ListView.FindItem(-1, NULL, lp1);
    nItem2 = g_ListView.FindItem(-1, NULL, lp2);
//(解説:直接g_ListViewという外部変数にアクセスするのでクラス内に置けず、外部関数としました) 

   //アイテム情報の取得
    g_ListView.GetItemText(nItem1, (int)lpSort, buf1, MAX_PATH);
    g_ListView.GetItemText(nItem2, (int)lpSort, buf2, MAX_PATH);
    if(sortsubno[(int)lpSort] == UP)
        return(strcmp(buf1, buf2));
    else
        return(strcmp(buf1, buf2) * -1);
//(解説:これがソートに必要な文字列比較の部分です。真であればstrcmpの1、0、-1を、偽なら-1、0、1を返します)

}

////////////////////////////
//カンマ区切り文字切り出し
//★str 文字列は切り出した文
//字列だけ短縮されるので注意
//(解説:表データのコンマ区切り文字列を切り出す関数です。詳細は以下にあります。)

////////////////////////////
char* GetDelimtStr(char* str) {

    static char buff[MAX_PATH * 4];    //取り敢えず1K bytes確保
    char *cp = str;
    int len = 0;
    switch(*cp) {
    case 0:
            return 0;            //NULL終端を指していれば単に帰る(FALSE)
    case '\"':                    //「""」処理
        cp++;                    //'\"'の次に進める
        while(*cp != '\"') {
            buff[len] = *cp;     //buffへのコピー
            cp++;
            len++;
        }
        cp++;                    //cpは'\"'の次の','へ進める
        buff[len] = '\0';        //buffのNULL終端処理
        break;
    default:                    //「""」以外
        //','でも'\n'でもNullでもなければ
        while(*cp != ',' && *cp != '\n' && *cp) {
            buff[len] = *cp;    //strデータをbuffにコピーしてゆく
            cp++;
            len++;
        }
        buff[len] = '\0';        //buffのNULL終端処理
        break;
    }
    if(*cp){                    //cpがNULL終端を指していなければ
        cp++;                    //','や'\n'を指しているので一つ進め
    }
    lstrcpy(str, cp);            //残余の文字列を文字列先頭に移動
    return buff;                //切り取った文字列を返す(TRUE)
}

//(解説:IDListでは、「メンバーシップ」「ログインID」「パスワード」「備考」という4列の文字列データを、

//  (フィル名IDList.dat)

//  data1,data2,data3,data4(cr("\n"))

//  data5,data6,data7,data8(cr("\n"))

//  data9,data10,data11,data12(cr("\n"))

//のようにコンマ区切り文字列データとして扱い、IDList.datとして読み書きします。(最終的にはこのファイルを暗号化して隠しファイルとしますが、それはもっと先のお話。)

//その為、読み込んだ文字列データを切り出してゆくことが必要ですが、この関数はヌル終端が来るまでstrが指す文字列データの','または'\n'あるいは'\0'で区切られた文字列をヌル終端させてbuffに切り出し、それ以降の文字列を冒頭から上書きさせて行く関数です。

//従って戻り値は切り出された文字列(buff)のポインター(TRUE)、またはヌル(FALSE)になります。

//なお、IDやパスワード等通常の語であれば空白文字が入らないのですが、「備考」にはスペースやCR(\n)があるので、そのような文字列は「ダブルクォテーションでくくる("~")」という仕様になっています。

 

さて、前回は(HTML Help Workshopは既にお蔵入り?とショックを受けて)唐突によもやま話に流れていっちゃいましたが、今回はちゃんとIDListのお話です。

 

rcファイル、Res.hファイル、cppファイルをカバーしましたので、次は要諦となるhファイルです。以下にSkeltonWizardが作成したものを、不要なところを削除せず、また(解説:)付きで載せてみました。(従って、特に断りがなければSkeltonWizardが作成したままのものです。)

 

//////////////////////////////////////////
// IDList.h
// Copyright (c) 09/29/2021 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResIDList.h"

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDI
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CSDI(UName) {}
    //メニュー項目、ダイアログコントロール関連
    bool OnOpen();
    bool OnSave();
    bool OnExit();
    bool OnAdd();
    bool OnSearch();
    bool OnEdit();
    bool OnDelete();
    bool OnSort();
    bool OnHelp();
    bool OnVersion();
    //ウィンドウメッセージ関連
    bool OnCreate(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
};

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

BEGIN_SDIMSG(IDList)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(IDList, IDM_OPEN, OnOpen())
    ON_COMMAND(IDList, IDM_SAVE, OnSave())
    ON_COMMAND(IDList, IDM_EXIT, OnExit())
    ON_COMMAND(IDList, IDM_ADD, OnAdd())
    ON_COMMAND(IDList, IDM_SEARCH, OnSearch())
    ON_COMMAND(IDList, IDM_EDIT, OnEdit())
    ON_COMMAND(IDList, IDM_DELETE, OnDelete())
    ON_COMMAND(IDList, IDM_SORT, OnSort())
    ON_COMMAND(IDList, IDM_HELP, OnHelp())
    ON_COMMAND(IDList, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    ON_CREATE(IDList)
    ON_NOTIFY(IDList)
    ON_SIZE(IDList)
    ON_CLOSE(IDList)
END_WNDMSG

//(解説:メインウィンドウの割り込み関数はすべて必要なものなので削除は不要です。)


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

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

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

//(解説:BCCSkeltonで定義されたコントロールクラス等のインスタンスを外部変数として、宣言により生成しています。)


///////////////////////////////////////////
// CDLGクラスからEDITDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class EDITDLG : public CDLG {
public:
    //コントロール関連
//    bool OnEdit1();
//    bool OnEdit2();
//    bool OnEdit3();
//    bool OnEdit4();

    bool OnIdok();
//    bool OnLabel1();
//    bool OnLabel2();
//    bool OnLabel3();
//    bool OnLabel4();

    bool OnIdcancel();
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

//(解説:コントロールにフォーカスが当たった場合の関数が多いですが、ボタン以外使いません。コメントアウトしてますが、削除してください。なお、コントロールにないのにIDCANCELから派生したOnIdcancel()を手作業で追加しているに注意してください。以下のダイアログでも同様です。)


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

BEGIN_MODALDLGMSG(editdlgProc, editdlg)    //第1引数がコールバック関数の名前
//    ON_COMMAND(editdlg, IDC_EDIT1, OnEdit1())
//    ON_COMMAND(editdlg, IDC_EDIT2, OnEdit2())
//    ON_COMMAND(editdlg, IDC_EDIT3, OnEdit3())
//    ON_COMMAND(editdlg, IDC_EDIT4, OnEdit4())

    ON_COMMAND(editdlg, IDOK, OnIdok())
//    ON_COMMAND(editdlg, IDC_LABEL1, OnLabel1())
//    ON_COMMAND(editdlg, IDC_LABEL2, OnLabel2())
//    ON_COMMAND(editdlg, IDC_LABEL3, OnLabel3())
//    ON_COMMAND(editdlg, IDC_LABEL4, OnLabel4())

    ON_COMMAND(editdlg, IDCANCEL, OnIdcancel())
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからSEARCHDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class SEARCHDLG : public CDLG {
public:
    char m_ToFind[MAX_PATH];
    //コントロール関連
//    bool OnEdit1();
    bool OnIdok();
//    bool OnLabel1();
    bool OnIdcancel();
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

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

BEGIN_MODALDLGMSG(searchdlgProc, searchdlg)    //第1引数がコールバック関数の名前
//    ON_COMMAND(searchdlg, IDC_EDIT1, OnEdit1())
    ON_COMMAND(searchdlg, IDOK, OnIdok())
//    ON_COMMAND(searchdlg, IDC_LABEL1, OnLabel1())
    ON_COMMAND(searchdlg, IDCANCEL, OnIdcancel())
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからSORTDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class SORTDLG : public CDLG {
public:
    //コントロール関連
//    bool OnCombobox1();
//    bool OnCombobox2();
//    bool OnLabel1();
//    bool OnLabel2();
 
   bool OnIdok();
    bool OnIdcancel();
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

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

BEGIN_MODALDLGMSG(sortdlgProc, sortdlg)    //第1引数がコールバック関数の名前
//    ON_COMMAND(sortdlg, IDC_COMBOBOX1, OnCombobox1())
//    ON_COMMAND(sortdlg, IDC_COMBOBOX2, OnCombobox2())
//    ON_COMMAND(sortdlg, IDC_LABEL1, OnLabel1())
//    ON_COMMAND(sortdlg, IDC_LABEL2, OnLabel2())

    ON_COMMAND(sortdlg, IDOK, OnIdok())
    ON_COMMAND(sortdlg, IDCANCEL, OnIdcancel())
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class VERSIONDLG : public CDLG {
public:
    //コントロール関連
//    bool OnVertxt();
    bool OnIdok();
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

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

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

 

SkeltonWizardでコントロールについては余計なものまでついてきますが、単に行を削除すればよいので手間にはなりません。

むしろIDCANCELというOSが定義している定数のコントロールが無いのに何故関数を入れたのか、が分かりにくいと思いますが、ダイアログはIDOKとIDCANCELが予め想定されており、たとえこのID定数のボタン等を付けなくても、「CR」キーや「ESC」キーがショートカット(アクセラレーター)として登録されているので「ユーザーがそれらのキーを押してしまうこともある」ことを想定してプログラミングする必要がある、ということを覚えておいてください。

 

この.hファイルがBCCFormandBCCSkeltonプログラムの最初に来ます。(それはコンパイルの中心となる.cppファイルのインクルードファイルの順に並ぶことからお判りでしょう。)従って、SheltnWizardが作成した以外の外部変数や外部関数を使用する場合には、(量が少なければ)この.hに入れてしまって結構です。(例えば、ツールバーのCTBAR TBar; ステータスバーのCSBAR SBar; コモンダイアログのCMNDLG cmndlg;はSkeltonWizardがそうしています。)しかし、それらの量が半端ない量である場合にはファイル構成を分かりやすくする意味で「User.h」というファイルにし、.cppファイルで.hとProc.hの間に入れてしまうことをお勧めします。(BCFEditorのヘルプ「BCCSkeltonについて」参照)

IDListについていえば、.cppファイルは次のように変更されてUser.hを入れています。

//////////////////////////////////////////
// IDList.cpp
//Copyright (c) 09/29/2021 by BCCSkelton
//////////////////////////////////////////
#include    "IDList.h"
#include    "User.h"
#include    "IDListProc.h"

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

 

さて、IDListで使うウィンドウ、ダイアログやコントロールクラスの定義、そのインスタンス(実体)の生成という導入を担うのが.hファイルで、定義されたクラスのメンバー関数の中身やその他の外部関数の中身はProc.h、User.hファイルで定義します。次回からプログラムに沿って少しづつそれらを解説してゆきます。

 

 

今日はIDList.hの解説をしようと思いましたが、IDListのヘルプファイルに関連して???が生じましたので、それに関して書いてみたいともいます。

 

1.ウィンドウズ(Windows)とは、IBMと袂を分かったMicrosoftが採用したGUIベースのOSです。(注)

注:1985年のWindows 1.0どころか、16bitベース最後の3.1(含む"for WorkGroup")まで知らない世代が増えたのであえて書きますが、シアトル・コンピュータ・プロダクツ(SCP)社から購入したCUIベースのOS、MS-DOSでIBMと組んで大儲けしたビル・ゲイツが、その延長線上では開発していったのが16bit Windowsですが、当時の8086系チップのメモリー制限(640KB)や不完全なマルチタスクから実用化にはまだ道のりがありました。一方1986年IBMと提携して開発したOS/2は完全なプリエンプティブ マルチタスクで、IBMは1991年に提携が終了した際に同社のPS/2マシン用に優位性があるOS/2をとり、Microsoftは性能の劣るWindowsを取りましたが、ワード、エクセル等のオフィスソフトやVisual Basic等の開発環境、Windowsのアプリケーションに集中し、1995年にインターネット対応32bit OSであるWindows 95が爆発的に売れ、最終的には独禁法違反問題まで論じられるMS帝国の礎になったOSとなりました。(2019年3月時点での世界のOSシェアは「Windows 10」が64%で1位。因みに私も当時ニューヨークでOS/2 Warpを買いましたが、2005年にサポート中止が宣言されました。)

 

2.ウィンドウズのヘルプ機能として、1990年のWindows 3.0からリッチテキストベースのWinHelpが添付されましたが、Windows 98あたりからHTMLベースのHTML Helpが登場し、セキュリティ問題を抱えるも現在はこれが主流、と理解していました。

 

3.しかし、現在の主流はHTML Helpではなく、Assistance MarkUp Language(MSML)だとのこと。知らないよ、そんなもの。しかし、現実問題HTML Helpは公式ページからダウンロードしようとしてもリンク切れエラーでできず(全て"404 - File or directory not found."となる。)、サードパーティのサイトからできるだけです。

(1)https://www.helpandmanual.com/downloads_mscomp.html
ダウンロードはできる。
(2)http://web.archive.org/web/20160201063255/http://download.microsoft.com/download/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe
ここでもダウンロードできる。
(3)http://web.archive.org/web/20160314043751/http://download.microsoft.com/download/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/helpdocs.zip
ヘルプ関連資料もダウンロードできる。
(4)https://ja.all10soft.com/html-help-workshop-windows-10/
これもあるが、最後のボタンが怖くてダウンロードしていない。

 

いずれにしてもWinHelp(winhelp.exeやwinhlp32.exe)はOSでブロックされて全く使えず、アマチュアが使えるヘルプはHTML Help(hh.exeと*.chmファイル) だけなので、これを使う想定で本ブログを書いてゆきますのであしからずご了解ください。

 

週末なので軽く。(明日の日曜はお休みします。)

 

既にIDList.rcはコードを公開している(「【IDList】リソースから作り始める」)ので、今日はResIDList.hと,SkeltonWizardが作成した4つのファイルのうちIDList.cppという簡単なものから解説しましょう。

 

1.ResIDList.h

ResIDList.hは、IDList.rcファイルの定数定義(注)ファイルです。IDList.rcやIDList.rcで規定するリソースを扱うプログラム本体のコンパイルの際に参照されます。

注:メニューやダイアログ等のリソースの部品である、メニューアイテムやコントロールは特定の為に定数を与えることが必要です。この為、「ファイルを開く」というメニュー項目はIDM_OPENというメニューアイテムとして特定したり、入力ダイアログの最初の文字入力用エディットコントロールをIDC_EDIT1として特定したりします。しかしこのまま(IDM_OPEN、IDC_EDIT1)ではコンパイラーにはわかりませんから400や100という固有の整数値を与えて特定しているわけです。(余談ですが、例えばIDM_OPENとIDM_EXIT等異なるリソースに間違って同じ整数値を与えると、ファイルを開こうとするとプログラムが終了する等、おかしな動作をすることになりますので注意してくださいね。)


//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_EDIT
#define    IDC_EDIT1        100
#define    IDC_EDIT2        101
#define    IDC_EDIT3        102
#define    IDC_EDIT4        103
#define    IDC_LABEL1        104
#define    IDC_LABEL2        105
#define    IDC_LABEL3        106
#define    IDC_LABEL4        107
// ダイアログ IDD_SEARCH
// ダイアログ IDD_SORT
#define    IDC_COMBOBOX1    200
#define    IDC_COMBOBOX2    201
// ダイアログ IDD_VERSION
#define    IDC_VERTXT        301

//---------------------
//  メニューリソース
//---------------------
// メニュー IDM_MAIN
#define    IDM_OPEN        400
#define    IDM_SAVE        401
#define    IDM_EXIT        402
#define    IDM_ADD            403
#define    IDM_SEARCH        404
#define    IDM_EDIT        405
#define    IDM_DELETE        406
#define    IDM_SORT        407
#define    IDM_HELP        408
#define    IDM_VERSION        409

//---------------------
//  イメージリソース
//---------------------
#define    IDI_ICON        500
#define    IDI_TOOLBAR        600

//---------------------
//  ストリングテーブル
//---------------------

//--------------------
//  アクセラレーター
//--------------------

//------------------
//  ヴァージョン情報
//------------------
 

なお、ダイアログの作成でIDOKとかIDCANCELとか既にWindowsが定義している定数を使った場合、BCCFormはこのヘッダーファイルに再定義しません。エラーにはならないので覚えておいてください。

 

2.IDList.cpp

次はIDListのプログラムの本体であるIDList.cppです。前にも述べましたがBCCSkeltonのマクロで簡単な記述になっているので、二重起動防止の辺りなどを整理するだけで完了です。

 

///////////////////////////////////////////////////////////
// IDList.cpp
// Copyright (c) 09/29/2021 by BCCSkelton
///////////////////////////////////////////////////////////
#include    "IDList.h"
#include    "User.h"
#include    "IDListProc.h"

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

    //2重起動防止
    if(!IDList.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)

//(解説:以下はSkeltonWizardがコメントとして記述したものから一行削除し、"//"を外しただけです。
        HWND hWnd = FindWindow("MainWnd", "IDList");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

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

    //ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
    //                        ExStyle, hParent, hMenu, x, y, w, h)
    if(!IDList.Create("IDList"))
        return 0L;

    //メッセージループに入る
    return IDList.Loop();
}

 

今日はここまで。なお、テキストの編集ができるので(前回まで気が付いていなかった-汗;)、項目を太字にして、(解説:)の色を変えてみました。読みやすいかな?