Win3.1for Workgroupが販売されなかった日本での32Bitシステムは、本格的なネットワーク対応ウィンドウシステムであるWin95から始まったと記憶していますし、事実Win95は爆発的に売れました。(私も1995年に米国で英語版を購入。)

 

そんな中、1996年に米国から帰国して再度シンガポールへ赴任するまでの3年間は、どういう訳かプライベートでのプログラミングは全くせず、(未だ社内イントラなどなかった)会社で米国から持ち帰ったPCをプリンターポートでつなぐネットワークデバイスを使ってWin95マシンを4台つなげた「課内LAN」を組み、MS Accessで仕事用のデータベースをいくつか作りました。自宅では当時流行っていたオーバードライブにはまり、自作PCでCeleronをオーバードライブさせて喜んでいました。(当時はバブルがはじけ、1997年の企業倒産が続く暗い時代でしたね。)

 

1999年にシンガポール赴任直後はまだPC自作が趣味で、シンガポールのダウンタウンの外れにSim Lim Square(森林商業中心)というビルがあり、パーツなら何でもあるということで遊びに行きました。シムリムスクウェアは、なんと「違法コピーソフト路上販売」のメッカで、私もゲームや言語等色々と楽しませていただきました。(日本で何十万もするWindows NT Serverが\1,000位。その後シンガポールが著作権保護を強化した2002-2003年に”路上販売のにーちゃん”も一斉にいなくなったのが、「シンガポール的」で印象深かったです。)

 

さて、PC自作にも飽き、毎月2-3回東南アジア諸国に出張する際の時間つぶしを兼ねてフリーのActiveBasic(当時はインタープリター)をダウンロードして、最初はCUIベースのものから次第にウィンドウベースのものと色々サンプルプログラムを作って遊んでいました。しかし逐次処理でプログラミングしてきた私には、基本的に割り込みによるWindowsプログラムが腑に落ちず、「なんでこうなるのだろう?」という好奇心が高じて、(偶然その時に始まった)Borland C++コンパイラーの無料配布を知り、当然のごとくダウンロードしましたが、それがWin32 C++ プログラミングにどっぷりまったきっかけでした。

 

最初は(某「〇でもわかるプログラミング」などを参考に)エディターでWindows SKDプログラムを書き、コンパイルするのですが、「なんで同じようなことを毎回長々と書くんだろう?」ということからSDIのスケルトンを書いておいてプログラムするようになり、また無料のコンパイラーではリソースがうまく扱えないことに不満を持ち始めました。そんな折に三浦淳さんのBCC Developerに出会い、その操作性の良さにほれ込む一方、リソースの方は「ないなら作っちゃおう」ということで、まずはリソースについて調べ、昔のBorlandのリソースエディターの出力形式を調べ、より使いやすいリソースエディターへとこつこつと改良、拡張して、2002年にVersion 2.41迄たどり着きます。(なお、この時に三浦さんにメールで連絡を取り、アイコンの使用許可をとって桃太郎BCC Developerの「お供のサル」としてBCCFormが完成します。)

 

また、BCCFormを開発する際に色々と作ったツールやBCCFormでつくったサンプルであるTBEditorやBCFEditor、EZImageなどを開発することになりますが、この時により「効率的な開発環境にならないものか?」という考えからBCCSkeltonの発想が生まれ、「リソースファイルからスケルトンを自動作成しよう」ということで生まれたのがSkeltonWizardです。この結果、BCCMakerでコンパイルすることもできるようになりました。(今回TextとSpeech等でEmbacadero C++ Builderのコンパイラーを使ったときはこれでコンパイルしています。)

 

まぁ、当時アラフィフで、プログラミングなどにうつつを抜かしてちゃいけなかったのですが、そういうことで2002年からVectorさんでBCCForm and BCCSkeltonを公表することになりました。

 

その後2004年に日本に帰国し、それこそ仕事仕事でプログラミングなど忘れてしまい、2011年の東日本大震災の年に役職定年後に関連会社へ出向、そこで65歳の完全定年を迎えて、郷愁からBCCForm and BCCSkeltonを見直し、アップデートしてリリースしたっていう経緯です。

 

ということで、ここ約20年のシステム環境変化(注)にはついて行けず、マシンも兄が自作したIntel Core i7 32Bitを使用しており、何をどう作っても、誰も余り関心を持たないことは重々承知ですが、それでもいまだに20年前の自作開発環境にしがみついております。

注:要すれば分散化がさらに進み、スマートフォンをベースとした「既にPCの時代ではない」、「既に難しいわりに、クラサバ系のC++はお呼びじゃない」ということです。(参考:「【2021年最新版】今学ぶべきプログラミング言語ランキング」)

 

以上のような経緯であり、「私の64Bit時代」は存在しません。悪しからずご承知ください。

 

前回書きましたように、1980年代はまだ家庭用は8Bitで、16Bit機は(値段からも)ビジネスシーンに限られていました。

実際、私も(お金がなかったので)「MS-DOS読本」のような本(だけ)を読んでコマンドやバッチ処理等を学習しましたが、「ペーパーユーザー」で実際にMS-DOSマシンに触れる機会は店頭のデモ機くらいでした。

 

今でも覚えている最初に16Bit機械を実際に使った機会は、1986年によんどころない事情で出社した日曜出勤の時でした。午前中に仕事は終わったのですぐ帰れたのですが、前々から「インテリジェントオンライン端末(要すれば日本の第一次オンラインのタイムシェアリングシステムのテレタイプではなく、メインフレームからぶら下がった和風MS-DOSマシン)」を「ユーティリティディスク(8インチフロッピーディスクという64KBのペラペラディスク)」でスタンドアロンで立ち上げてみたい、という思いがあったのです。平日はいつも忙しかったので、その思いはかなわず、この日曜出勤が丁度よい機会でした。

 

さて、ユーティリティディスクでインテリジェントオンライン端末を立ち上げると画面には"MS-DOS Ver 2.0 Copyright by Microsoft Corporation"と出るじゃありませんか。さっそくペーパー知識を総動員して"dir"だの、"copy"、"rename"、"del"だのDOSコマンドを打って確認し、メーカーが用意したCUIベースのワードプロセッサーやスプレッドシートを使ってみました。(使い勝手?まぁ、敢えて名を秘しますが『日〇』さんのソフトウェアは、その後会社が導入したグループウェアを含め、余り...。)

 

本格的に使うようになったのはその2年後の1988年に海外業務を担当するようになって、純正の日本語IBM PC/2(即ち日本語PC DOS = MS-DOSマシン)が使えるようになり、俺ス本デンスで日常的に英文ワードプロセッサー(Word Star)とカード型データベースを使うようになってからです。この時は(日曜出勤でも)仕事が忙しすぎてIBMマシンで遊ぶことなど考えられもしませんでした。

 

そして3年後の1991年に米国現法の経営(と同時に現場業務遂行)の為に米国に赴任します。

 

この時は前任者と協議し、東芝の(日本語MS-DOSと英語MS-DOSが共に走る)ラップトップパソコンとCUIベースの「ワープロ」「表計算」「データベース」が入った統合ソフト日本語Microsoft Worksを日本の秋葉原で最安値で(米国現法名義で)購入し、赴任時に持ち込むこととしました。(この時の東芝のインテル80286(注)デスクトップの値段が、雑誌の広告で45万でした。)

注:インテルチップの歴史の最初に出ていますが、本当は8086という悪名高きセグメント切り替えによる最大メモリーアクセス空間640KBのチップがあり、その後1MBまでアクセスできる、という触れ込みで出てきた「最新チップ」だったと記憶します。

 

米国に行ってびっくりしたのが、既にIBM互換PCが数多く出回っており、80286マシンだと(実際にプライべートで購入しましたが)12万位で、MS-DOS5、QuickBasic等が同梱され、更にその後16Bitmマシンで一般的となるWindows3.1も標準装備であったことです。こんなに安いので、80386、80486、Pentiumとアップグレードされる度に買い替え、自宅でdBaseIVで業務用システムを開発したり、ゲームで遊んだり、QuickBasicや(この時初めて購入した言語がBorland C++の原型となる)Turbo C++などでプログラミングもしていました。ある意味、この時はPCユーザー的に贅沢な時代でした。

 

その後伝手で日本語Windows3.1を購入し、(日本では販売されなかった)英語版Windows3.1 for Work Groupを切り替えて使用するようになり、日本語Windous環境で英語ソフトも走ることを確認しました。(もう日本から苦労して持って行った東芝の80286ラップトップは遅いし、使いづらいので2年でお払い箱になりました。)同時に、PtoPネットワークを業務環境に取り入れ、私、米国人社内弁護士、秘書2名でデータベースを共用するようになりました。(しかし、ソフト開発のみならず、各機械への導入や保守、ハード的には配線もすべて会社の最高トップの私自ら手作業で行い、ネットワークケーブルは床に這わせてガムテープで固定していました。)

 

まぁ、この時点で既に16Bitというよりは(ハード的には)32Bitの世界なのですが、ソフト的には相変わらず16BitのWindows3.xを使っていました。しかしWindows95の導入で本格的な32Bit時代となるのですが、日本においては1996年に帰国してから、ということになります。

 

この間HTML Help WorkshopでWindows(Microsoft)とOS/2(IBM)の話をしましたが、今日はもっと昔のお話を。

 

私がコンピューターに触れたのは、28歳で所帯をもった後実家に行った際に、兄貴のシャープのポケコン(注)に触った時でした。

注:確かこんな奴だった。とても簡単なBASICがついており、プログラムはデータレコーダー(テープレコーダー)で記憶していました。

 

面白いので、兄からかっぱらって仕事場に持ち込み、定型の四則演算業務をやらせて結果を印字出力させていました。しかし、更なる「高度なBASIC」でプログラミングしたくなり、へそくりをためて初めて買ったのが、ソニーのHitBitというMSXマシン(注)。

注:これです。MSXというのはビルゲイツと西和彦が組んで日本のホームPCを席捲しようという野望戦略でした。ザイログ社のZ80 4MHチップでテレビにテキストのみならず、画像、音楽等の出力ができて、基本は内蔵ROMのBASICですが、カートリッジでDOS(MS-DOSとコマンド、ディスクフォーマット互換のMSX-DOS)に拡張できる、という、当時NECが8 Bitマシンを2-30万、MS-DOSマシンを40~100万で販売していた時代としては破格のコストパフォーマンスでしたね。しかし、2DKのアパート住まいで、生まれたばかりの長男が邪魔する中、キー入力でのプログラミングは困難を極め、入力中や録音中(プログラムをセーブするときにピーヒョロロと鳴ります)に目を離すと長男がキーボードをバンバンと手でたたき、父は涙していました。

 

人間の欲は果てしがないもので、MSXの画像の粗さ(512X256)に飽き足らず、矢張り一級品の640X400(未だPC互換の640X480ではなかった)が欲しくなり、且つディスク(といってもフロッピーです)とスピードも求めたことから、当時最速のZ80 6MH、MS-BASIC互換、通信(RS-232Cインターフェースによる電話通信です)可などが売りだったシャープの「スーパーMZ(MZ-2500)」を購入。最大256色(320X200)や16色高細精度画像(640X400)、音響や(当時としては珍しい)マウスインターフェース標準装備等、楽しくて、楽しくて、色んなものをプログラムし、当時のWindows1.0をまねた、ドロップダウンメニューでFileやEditのディスク基本操作をするGUI環境も作りました。

 

しかし、如何にMS=BASIC互換で当時最速のBASIC-M25といえども、インタープリター言語であり、Z80ネイティブの機械語でプログラミングするには、BASICで組んだモニター(注)で1バイトづつ16進数を打ち込むしかありませんでした。

(注)Z80の「広大(!)な64Kバイトメモリー空間(0~0FFFFH)」を、アドレスを指定してPeek関数で読み出し(Dump)、Poke関数で書き込むというプログラム。

これでは本当のプログラミングができない、という焦りがあり、当時カーニハン・リッチーの"C Primer"(邦題「プログラミング言語C」)を読んでいたこともあり、(またまた女房に内緒でへそ喰って)MZ-2500用のPersonal CP/MとCのサブセット(注)であるBDS-Cを購入しました。

注:色々と制限がありましたが、最大のものは「整数だけで、実数が使えない」kとでした。しかしMZ-2500にはIOCS(入出力制御システム-グラフィック等も含むSVCコールと実数での算術計算を行うFNCコール)が提供されていて、全く苦になりませんでした。実際、MZ-2500とBDS-Cを使い、SVCコールグラフィックを、FNCコールで実数を扱ったサンプルプログラムも組んでいました。

 

BDS-Cを使った最初のCプログラムは、なんとZ80アッセンブラー(「ZASM」と命名していました。現在Z80アセンブラーエミュレーターに同名のものがあります)というもので、ソースファイル(*.zsm)を読み込み、第1passでラベルをDB登録して、命令を解釈し、第2passで実行可能なcomファイル(注)を出力するものです。なお、ZCASMというプログラムを使い、ZASMでアセンブルして作る実行ファイル(com)をリンクファイル(crl)に名前変更して、BDS-Cでリンクすることもできましたので、MZ-2500のIOCS(入出力制御システム)をすべて使いこなせました。

注:BASIC-M25等では機械語の実行ファイルの拡張子はobjですが、CP/MではMS-DOSと同じくcomになります。

 

このようにMZ-2500でプログラミングを援助していましたが、世の中で8Bitマシンが使われたのも1980年代までで、本格的なIBM互換PCが市場に出た1990年代には駆逐されました。実際私は1991年に米国に駐在するまで(当時の日本のMS-DOSマシンは45万位しましたので)MZ-2500で遊んでいましたが、米国でIBM互換PCを使うようになって帰国した1996年には「MZ-2500は郷愁をもって眺めるだけ」のマシンとなり、場所ふさぎだからと処分されてしまいました。(泣;)

 

IDListの完成版です。

初めて起動すると、

という注意書きが出ます。構わずOKボタンを押すとブランクのウィンドウが出ます。

 

一方、既にIDList.datファイルが存在すると、「データの復号化」というタイトルの↓のダイアログが出ますので、OKを押します。

このダイアログは、セキュリティレベルを「高」にしてパスワードを設定していると↓のようなパスワード要求ダイアログとなりますので、その際はパスワードIDに設定したパスワードを入力します。

 

起動した後、データを追加するにはリストビューの未入力行をダブルクリックするか、またはツールバーの「+」ボタンを押す、あるいは「編集」「追加」メニューを選択してデータを追加してゆきます。

 

「検索」は、ツールバーの双眼鏡ボタンを押すか「編集」「検索」メニューで検索ダイアログを開いて検索文字を入力します。

いずれかの列のセルに検索文字列を含む行を選択し、該当がなくなるまで継続します。

 

「編集」は、選択行をダブルクリックするか、行を選択してツールバーの鉛筆ボタンを押す、または「編集」「編集」メニューで、「IDとパスワードの編集」というタイトルの編集ダイアログ(上の「IDとパスワードの追加」ダイアログと同じもの)で行データを読み込みます。

 

「削除」は行を選択(複数行も可)し、ツールバーの削除ボタンを押すか、「編集」「削除」メニューを選択して実行します。(「元に戻す」はないので、気を付けて!)

 

リストビューの行のソートをする場合、列タイトルをクリックすると「昇順」「降順」のトグルで並び替えられますし、ツールバーの「A←→Z]ボタンを押すか、メニューの「表示」「並び替え」で専用ダイアログによって

並び替えることもできます。

 

ツールバーの「?」ボタンを押すか、ヘルプの「本ソフトの使い方」を選択すると「(ヘルプファイルパス、名の)ファイルが開けません」というエラーダイアログが出ます。IDList.exeと同じフォールダーにHTMLヘルプワークショップでつくった"IDListHelp.chm"ファイルを作って入れておけば表示されます。

 

IDListを終了させるには、ツールバーのドアボタンを押すか、「ファイル」「終了」を選択するか、システムボタン「X」を押してください。するとIDListは自動的に暗号化したIDList.datを書き込もうとして↑の暗号化ダイアログを表示します。

(A)中程度のセキュリティレベルでよければそのまま「OK」を、

(B)高程度のセキュリティレベルであれば、セキュリティレベルを選択してパスワードを入力しますが、

(C)IDList.datに特に変更を加えていない、または変更したくない場合には「キャンセル」をして「データの暗号化に失敗しました」エラーを出して書き換えを回避してください。

 

最後は解説付きのIDListProc.hとIDList.bdpです。

 

【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, "選択メンバーシップ");

    //リストビューコントロールの作成
    //複数選択を許さない場合、スタイルに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, "備考");

    //メニューハンドルの取得
    g_hPopup = GetSubMenu(GetMenu(m_hWnd), 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);

    //CHMヘルプファイルの準備
    CARG arg;
    g_HelpFile = "hh \"";
    g_HelpFile = g_HelpFile + arg.Path();
    g_HelpFile = g_HelpFile + "\\IDListHelp.chm";

    //データファイルパス、名
    g_File = arg.Path();
    g_File = g_File + "\\IDList.dat";

    //起動時ファイル読込処理
    if(!DataLoad(m_hWnd, g_File.ToChar()))
        MessageBox(m_hWnd, "まだIDList.datがつくられていないか\n読み込み処理が中断されました。", "注意", MB_OK | MB_ICONWARNING);

(解説:g_FileにIDList.datのファイルパス、名を入れ、暗号化ファイル(DataLoadの第3引数を省略するとTRUE)を読み込みます。)


    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;
        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();
    //最後の「備考」幅を調整して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;
}

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

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES) {
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        //終了時ファイル書込処理
        DataSave(m_hWnd, g_File.ToChar());
        //データファイル属性を隠しファイルとする
        DWORD attrib = GetFileAttributes(g_File.ToChar());    //属性を取得
        attrib |= FILE_ATTRIBUTE_HIDDEN;    //隠しファイル属性を追加
        SetFileAttributes(g_File.ToChar(), attrib);

(解説:暗号化(第3引数省略でTRUE)ファイルを書き込みます。なお、パスワード等特に書き換える必要が無ければダイアログはキャンセルしてください。)

        return TRUE;
    }
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

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

    //データファイルの読み込み
    char* cp = cmndlg.GetFileName(m_hWnd, "datファイル(*.dat)\0*.dat\0全てのファイル(*.*)\0*.*\0\0", TRUE, "dat", ".");
    if(cp) {
        bool cyph = (MessageBox(m_hWnd, "復号化しますか?", "復号化", MB_YESNO | MB_ICONQUESTION) == IDYES);
        DataLoad(m_hWnd, cp, cyph);

(解説:至極あっさりとしました。)

        return TRUE;
    }
    else {
        MessageBox(m_hWnd, "ファイルが選択されていません", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnSave() {

    //ファイルの保存場所を指定する
    char* cp = cmndlg.GetFileName(m_hWnd, "datファイル(*.dat)\0*.dat\00",
                        FALSE, "dat", ".");
    if(cp) {
        //ファイルの書き込み
        bool cyph = (MessageBox(m_hWnd, "暗号化しますか?", "暗号化", MB_YESNO | MB_ICONQUESTION) == IDYES);
        DataSave(m_hWnd, cp, cyph);

(解説:至極あっさりとしました。)

        return TRUE;
    }
    else {    
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnExit() {

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

bool CMyWnd::OnAdd() {

    //追加の場合は、データ受け渡し用のg_Data(CSTR)を空(NULL)にする
    g_Data = "";
    //編集用ダイアログを追加用ダイアログとして使う
    if(!editdlg.DoModal(m_hWnd, "IDD_EDIT", editdlgProc) ||
        !*g_Data.ToChar())                //キャンセルされた場合、または
        return FALSE;                    //データが空の場合
    int n = g_ListView.GetItemCount();    //リストの最後尾を確認
    //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);
    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);
    //前回の検索と同じ検索文字列であれば検索を継続
    if(g_Data != ToFind) {    //異なれば
        ToFind = g_Data;        //検索文字列を更新し
        found = 0;            //最初から検索する
        g_ListView.SetItemState(-1, 0, LVIS_SELECTED);    //全行の選択状態の解除
    }
    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;
}

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 + "\"";    //「""」で囲う
    //編集用ダイアログを呼び出す
    if(!editdlg.DoModal(m_hWnd, "IDD_EDIT", editdlgProc))
        return FALSE;;
    //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);
    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);
    }
    SBar.SetText(1, "選択メンバーシップ");    //ステータスバー表示変更

    return TRUE;
}

bool CMyWnd::OnSort() {

    sortdlg.DoModal(m_hWnd, "IDD_SORT", sortdlgProc);
    return TRUE;
}

bool CMyWnd::OnHelp() {

    WinExec(g_HelpFile.ToChar(), SW_SHOWNORMAL);    //g_HelpFileがコマンドライン
    return TRUE;
}

bool CMyWnd::OnVersion() {

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

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

    if(!*g_Data.ToChar()) {
        SendMsg(WM_SETTEXT, 0, (LPARAM)"IDとパスワードの追加");
        SendItemMsg(IDOK, WM_SETTEXT, 0, (LPARAM)"登録終了");
        return TRUE;
    }
    //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); 
    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);
    return TRUE;
}

bool EDITDLG::OnIdcancel() {

    EndModal(FALSE);
    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);
    return TRUE;
}

bool SEARCHDLG::OnIdcancel() {

    EndModal(FALSE);
    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);
    //ソート対象項目に昇順、降順のデータを書込む
    sortsubno[col] = dir;
    //ソート処理
    g_ListView.SortItem(CompProc, col);
    EndModal(TRUE);
    return TRUE;
}

bool SORTDLG::OnIdcancel() {

    EndModal(FALSE);
    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);
    return TRUE;
}

 

【IDList.bdp】

[Project]
Version=1.2.21
Count=5
Filename1=IDList.h,1,IDList.bdp,1,IDList.h
Filename2=User.h,1,IDList.bdp,1,User.h
Filename3=IDListProc.h,1,IDList.bdp,1,IDListProc.h
Filename4=IDList.cpp,1,IDList.bdp,1,IDList.cpp
Filename5=ResIDList.h,1,IDList.bdp,1,ResIDList.h

[Compile]
ResourceName=IDList.rc
RunParam=
Count=2
Current=2

[Compile1]
DefName=Debug
ExeName=IDList.exe
MakeName=IDList.mak
NoMakefile=0
UseMakefileEx=0
Target=-WC
TargetSub=
InstructionSet=-6
Optimize=-Od
Warnning=-w-
Debug=-v -y
Compiler=-k -b
PreHeader=-H-
Call=-pc
Language=-AT
Option=
IncludePath=C:\Borland\bcc55\Lib
LibraryFile=
Define=DEBUG
ResIncludePath=
OutputDir=Debug
MakeBefore=
MakeAfter=

[Compile2]
DefName=Release
ExeName=IDList.exe
MakeName=IDList.mak
NoMakefile=0
UseMakefileEx=1
Target=-W
TargetSub=
InstructionSet=-6
Optimize=-O1
Warnning=-w-
Debug=
Compiler=-k -b
PreHeader=-H-
Call=-ps
Language=-AT
Option=
IncludePath=C:\Borland\BCCForm
LibraryFile=
Define=
ResIncludePath=
OutputDir=Release
MakeBefore=
MakeAfter=

[OpenFiles]
FileCount=6
FileName1=IDList.h
FileName2=User.h
FileName3=IDListProc.h
FileName4=IDList.cpp
FileName5=ResIDList.h
FileName6=C:\Users\(パス)\IDList\IDList.rc
ActivePageIndex=0
 

 

次はIDList.h、解説付きのUser.hとIDList.cppです。

 

【IDList.h】

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

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

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

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

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

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

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

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

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

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

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

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

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

【User.h】

//////////////////////////////////////////
// User.h
// Copyright (c) 09/29/2021 by Ysama
//////////////////////////////////////////
/* オブジェクト ファイル内にライブラリ検索レコードを配置します。
   このコメントの種類には、リンカーで検索するライブラリの名前 
   (および場合によってはパス) を持つコメント文字列パラメーターを
   指定する必要があります。
*/
#pragma comment (lib, "crypt32.lib")

(解説:ここに#pragma commentを置きましょう。)
//定数の定義
#define    IDC_LISTVIEW 1000
#define UP 0
#define DOWN 1
#define NO_OF_SUBITEM 4

////////////////////////
//リストビューの作成
////////////////////////
CLISTVIEW g_ListView;

//////////////////////////
//データファイル用CSTR変数
//////////////////////////
CSTR g_Data;

////////////////////////////
//データファイル名用CSTR変数
////////////////////////////
CSTR g_File;

(解説:自動読書き用のファイル名を記録するCSTRクラス外部変数です。)
//////////////////////////
//ヘルプファイル用CSTR変数
//////////////////////////
CSTR g_HelpFile;

////////////////////////////////
//ポップアップメニュ-のハンドル
////////////////////////////////
HMENU g_hPopup;

//////////////////////////
//サブアイテムのソート関連
//////////////////////////
//列とソート方法の配列
int sortsubno[NO_OF_SUBITEM];
//ソートコールバック関数
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.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);
}

////////////////////////////
//カンマ区切り文字切り出し
//★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)
}

(解説:前回やった暗号化ファイル読書き関数はここ↓に配置しましょう。)
///////////////
//ファイル処理
///////////////
bool DataLoad(HWND hWnd, char* fn, bool cyph = TRUE) {

    //データファイルの読み込み
    if(cyph) {     //データが暗号化されている場合
        //ファイルを読み込みでオープン
        bool bSuccess = FALSE;                        //処理結果フラグ
        HANDLE hFile = CreateFile(fn, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
        if(hFile != INVALID_HANDLE_VALUE) {            //オープンできた場合
            DWORD dwFileSize = GetFileSize(hFile, NULL);
            if(dwFileSize != 0xFFFFFFFF) {            //-1は(0xFFFFFFFF)エラー
                char* dat = new char[dwFileSize];    //データバッファを確保する
                DWORD dwRead;
                if(ReadFile(hFile, (LPVOID)dat, dwFileSize, &dwRead, NULL)) {
                    DATA_BLOB blobIn;
                    blobIn.cbData = dwRead;            //暗号化するデータのサイズ
                    blobIn.pbData = (BYTE*)dat;        //暗号化するデータのポインター
                    DATA_BLOB blobOut;
                    CRYPTPROTECT_PROMPTSTRUCT promptStruct;
                    promptStruct.cbSize = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
                    promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
                    promptStruct.hwndApp = hWnd;
                    promptStruct.szPrompt = L"データの復号化";
                    if(!CryptUnprotectData(&blobIn, NULL, NULL,
                                            NULL, &promptStruct, 0, &blobOut))
                        MessageBox(hWnd, TEXT("データの復号化に失敗しました。"),
                                    "エラー", MB_OK | MB_ICONERROR);
                    else {
                    //blobOut.pbDataが復号化データ、blobOut.cbDataはそのサイズ
                        g_Data = (char*)blobOut.pbData;
                        LocalFree(blobOut.pbData);    //blobOutのメモリーを解放
                        bSuccess = TRUE;
                    }
                }
                delete [] dat;                        //バッファを廃棄
            }
            CloseHandle(hFile);                        //ファイルのクローズ
        }
        if(!bSuccess)                                //g_Dataに読み込めなかった場合
            return bSuccess;
    }
    else {     //データが暗号化されていない場合
        if(!g_Data.FromFile(fn))                    //読み込めなかった場合
            return FALSE;
    }
    //データ読み出し-行読み出しを繰り返す
    char* cp;
    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++;
    }
     return TRUE;
}

bool DataSave(HWND hWnd, char* fn, bool cyph = TRUE) {

    //データの作成
    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";
    }
    //データ(g_Data)ファイルの書き込み
    if(cyph) {     //データを暗号化する場合
        DATA_BLOB blobIn;
        blobIn.cbData = strlen(g_Data.ToChar()) + 1;    //暗号化するデータのサイズ
        blobIn.pbData = (BYTE*)g_Data.ToChar();            //暗号化するデータのバイトポインター
        DATA_BLOB blobOut;
        CRYPTPROTECT_PROMPTSTRUCT promptStruct;
        promptStruct.cbSize = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
        promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
        promptStruct.hwndApp = hWnd;
        promptStruct.szPrompt = L"データの暗号化";
        if(!CryptProtectData(&blobIn, NULL, NULL, NULL, &promptStruct, 0, &blobOut)) {
            MessageBox(hWnd, TEXT("データの暗号化に失敗しました。"), "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }
        else {     //{blobOut.pbDataが暗号化されたデータ、blobOut.cbDataはそのサイズ
            //属性がFILE_ATTRIBUTE_NORMALでなければFILE_ATTRIBUTE_NORMALにする
            DWORD attrib = GetFileAttributes(fn);
            if(attrib != FILE_ATTRIBUTE_NORMAL)
                SetFileAttributes(fn, FILE_ATTRIBUTE_NORMAL);
            //ファイルを書込みでオープン
            bool bSuccess = FALSE;                //処理結果フラグ
            HANDLE hFile = CreateFile(fn, GENERIC_WRITE, NULL, NULL,
                                CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            if(hFile == INVALID_HANDLE_VALUE)    //オープン失敗
                MessageBox(hWnd, "ファイルを開けられませんでした。", "エラー", MB_OK | MB_ICONERROR);
            else {
                DWORD dwWritten;
                bSuccess = WriteFile(hFile, blobOut.pbData, blobOut.cbData, &dwWritten, NULL);
                CloseHandle(hFile);                //ファイルのクローズ
            }
            LocalFree(blobOut.pbData);            //blobOutのメモリーを解放
            SetFileAttributes(fn, attrib);        //属性を戻す
            return bSuccess;                    //ファイルの書き込み結果を返す
        }
    }
    else
        return g_Data.ToFile(fn);    //ファイルの書き込み結果を返す
}

 

【IDList.cpp】

///////////////////////////////////////////////////////////
// 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例)
        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();
}
 

 

前回やった暗号化読書き関数を組み込み、隠しデータファイルを自動読書きする完成版の全ソースを今後3回に亘って載せます。また最終回には使用方法を説明します。

 

書き込みスペースの関係上まずはリソース関係から。これらのファイルはBDDFormで確認できます。

 

【IDList.rc】

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

//----------------------------------
// ダイアログ (IDD_EDIT)
//----------------------------------
IDD_EDIT DIALOG DISCARDABLE 0, 0, 270, 150
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "IDとパスワードの編集"
FONT 8, "MS 明朝"
{
 CONTROL "", IDC_EDIT1, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 87, 9, 171, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT2, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 87, 30, 171, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT3, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 87, 48, 171, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT4, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | WS_VSCROLL | ES_LEFT, 87, 66, 171, 48
 CONTROL "編集終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 198, 123, 60, 18
 CONTROL "メンバーシップ", IDC_LABEL1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 9, 75, 15
 CONTROL "ログインID", IDC_LABEL2, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 30, 75, 15
 CONTROL "パスワード", IDC_LABEL3, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 48, 75, 15
 CONTROL "備考(任意)", IDC_LABEL4, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 66, 75, 15
}

//----------------------------------
// ダイアログ (IDD_SEARCH)
//----------------------------------
IDD_SEARCH DIALOG DISCARDABLE 0, 0, 135, 48
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "検索"
FONT 8, "MS 明朝"
{
 CONTROL "", IDC_EDIT1, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 3, 15, 126, 12, WS_EX_CLIENTEDGE
 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 45, 30, 45, 15
 CONTROL "検索する文字列", IDC_LABEL1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 3, 123, 9
}

//----------------------------------
// ダイアログ (IDD_SORT)
//----------------------------------
IDD_SORT DIALOG DISCARDABLE 0, 0, 144, 87
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "データの並べ替え"
FONT 8, "MS 明朝"
{
 CONTROL "", IDC_COMBOBOX1, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 12, 18, 120, 60
 CONTROL "", IDC_COMBOBOX2, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 12, 48, 120, 60
 CONTROL "サブアイテムの選択", IDC_LABEL1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 15, 6, 93, 9
 CONTROL "並べ替え方法", IDC_LABEL2, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 15, 36, 93, 9
 CONTROL "完了", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 51, 66, 39, 15
}

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------
IDD_VERSION DIALOG DISCARDABLE 0, 0, 160, 40
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "バージョン情報"
FONT 9, "Times New Roman"
{
 CONTROL IDI_ICON, 0, "STATIC", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 12, 10, 32, 32
 CONTROL "", IDC_VERTXT, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 42, 8, 80, 24
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 14, 20, 12
}

//-------------------------
// メニュー(IDM_MAIN)
//-------------------------
IDM_MAIN MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "データを開く(&O)", IDM_OPEN
        MENUITEM SEPARATOR
        MENUITEM "データの保存(&S)", IDM_SAVE
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "編集(&E)"
    {
        MENUITEM "追加(&A)", IDM_ADD
        MENUITEM "検索(&S)", IDM_SEARCH
        MENUITEM "編集(&E)", IDM_EDIT
        MENUITEM "削除(&D)", IDM_DELETE
    }
    POPUP "表示(&V)"
    {
        MENUITEM "並び替え(&S)", IDM_SORT
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "本ソフトの使い方(&H)", IDM_HELP
        MENUITEM SEPARATOR
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }

}

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

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------
IDI_TOOLBAR    BITMAP    DISCARDABLE    "C:\Users\(パス)\IDList\ToolBar.bmp"

 

【ResIDList.h】

//-----------------------------------------
//             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

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

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

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

現在のIDListは、データの読み書きがBCCSkeltonのCSTRクラス変数、g_DataのFromFile()、ToFile()のメンバー関数でテキストファイルになっています。これを前回のDPAPI関数を使って暗号化もできるようにしてみましょう。(テキストファイルだけは危険ですが、暗号化ファイルのみでは使いづらいので「ファイルを開く」「ファイルの保存」では選択できるようにします。)

 

現在は、CMyWnd::OnOpen()とOnSave()にすべて書いていますが、外部変数を使っていることもあり、User.hに暗号化区分を指定できるようにした外部関数として持ち出し、DataLoad、DataSave関数と呼びます。

 

///////////////
//ファイル処理
///////////////
bool DataLoad(HWND hWnd, char* fn, bool cyph = TRUE) {
(解説:引数は順に、親ウィンドウハンドル、ファイル名(文字列)、暗号化有無(規定値は暗号化)にしています。)


    //データファイルの読み込み
    if(cyph) {     //データが暗号化されている場合
        //ファイルを読み込みでオープン
        bool bSuccess = FALSE;                        //処理結果フラグ
        HANDLE hFile = CreateFile(fn, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
        if(hFile != INVALID_HANDLE_VALUE) {            //オープンできた場合
            DWORD dwFileSize = GetFileSize(hFile, NULL);
            if(dwFileSize != 0xFFFFFFFF) {            //-1は(0xFFFFFFFF)エラー
                char* dat = new char[dwFileSize];    //データバッファを確保する
                DWORD dwRead;
                if(ReadFile(hFile, (LPVOID)dat, dwFileSize, &dwRead, NULL)) {
                    DATA_BLOB blobIn;
                    blobIn.cbData = dwRead;            //暗号化するデータのサイズ
                    blobIn.pbData = (BYTE*)dat;        //暗号化するデータのポインター
                    DATA_BLOB blobOut;
                    CRYPTPROTECT_PROMPTSTRUCT promptStruct;
                    promptStruct.cbSize = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
                    promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
                    promptStruct.hwndApp = hWnd;
                    promptStruct.szPrompt = L"データの復号化";
                    if(!CryptUnprotectData(&blobIn, NULL, NULL,
                                            NULL, &promptStruct, 0, &blobOut))
                        MessageBox(hWnd, TEXT("データの復号化に失敗しました。"),
                                    "エラー", MB_OK | MB_ICONERROR);

(解説:この"TEXT"は マクロで、UNICODEが定義されていれば文字列に 'L' を付加します。)
                    else {
                    //blobOut.pbDataが復号化データ、blobOut.cbDataはそのサイズ
                        g_Data = (char*)blobOut.pbData;
                        LocalFree(blobOut.pbData);    //blobOutのメモリーを解放
                        bSuccess = TRUE;
                    }
                }
                delete [] dat;                        //バッファを廃棄
            }
            CloseHandle(hFile);                        //ファイルのクローズ
        }

(解説:読込だけですが、DPAPIプログラムでやった通りですね。注意すべきはdatとblob.pbDataで確保されたメモリーを解放することです。)
        if(!bSuccess)                                //g_Dataに読み込めなかった場合
            return bSuccess;
    }
    else {     //データが暗号化されていない場合
        if(!g_Data.FromFile(fn))                    //読み込めなかった場合
            return FALSE;
    }

(解説:暗号化されていない処理は従前の通りです。)
    //データ読み出し-行読み出しを繰り返す
    char* cp;
    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++;
    }
(解説:リストビューへの表示も変わりません。)
     return TRUE;
}

bool DataSave(HWND hWnd, char* fn, bool cyph = TRUE) {

 

(解説:これも引数は親ウィンドウハンドル、ファイル名、暗号化フラグと同じにしています。)

    //データの作成
    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";
    }
(解説:リストビューからg_Dataへの格納も変わりません。)

    //データ(g_Data)ファイルの書き込み
    if(cyph) {     //データを暗号化する場合
        DATA_BLOB blobIn;
        blobIn.cbData = strlen(g_Data.ToChar()) + 1;    //暗号化するデータのサイズ
        blobIn.pbData = (BYTE*)g_Data.ToChar();            //暗号化するデータのバイトポインター
        DATA_BLOB blobOut;
        CRYPTPROTECT_PROMPTSTRUCT promptStruct;
        promptStruct.cbSize = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
        promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
        promptStruct.hwndApp = hWnd;
        promptStruct.szPrompt = L"データの暗号化";
        if(!CryptProtectData(&blobIn, NULL, NULL, NULL, &promptStruct, 0, &blobOut)) {
            MessageBox(hWnd, TEXT("データの暗号化に失敗しました。"), "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }
        else {     //{blobOut.pbDataが暗号化されたデータ、blobOut.cbDataはそのサイズ
            //属性がFILE_ATTRIBUTE_NORMALでなければFILE_ATTRIBUTE_NORMALにする
            DWORD attrib = GetFileAttributes(fn);
            if(attrib != FILE_ATTRIBUTE_NORMAL)
                SetFileAttributes(fn, FILE_ATTRIBUTE_NORMAL);
(解説:ここは追加部分です。IDList.datという自動読書きファイルは隠しファイル属性を付けるつもりなので、ノーマル属性ではない場合、エラーを回避する為に、一旦属性をattribに記録してノーマル属性にします。)

            //ファイルを書込みでオープン
            bool bSuccess = FALSE;                //処理結果フラグ
            HANDLE hFile = CreateFile(fn, GENERIC_WRITE, NULL, NULL,
                                CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            if(hFile == INVALID_HANDLE_VALUE)    //オープン失敗
                MessageBox(hWnd, "ファイルを開けられませんでした。", "エラー", MB_OK | MB_ICONERROR);
            else {
                DWORD dwWritten;
                bSuccess = WriteFile(hFile, blobOut.pbData, blobOut.cbData, &dwWritten, NULL);
               CloseHandle(hFile);                //ファイルのクローズ
            }
            LocalFree(blobOut.pbData);            //blobOutのメモリーを解放
            SetFileAttributes(fn, attrib);        //属性を戻す
(解説:ここも追加部分で、ファイル属性を書き込み前に戻します。)

            return bSuccess;                    //ファイルの書き込み結果を返す
 

次回はこれらの関数を使ってIDListProc関数を修正してIDListを完成させます。

 

DPAPIのプログラム解説最終回は、動作の中核となるDPAPIProc.hです。

 

//////////////////////////////////////////
// DPAPIProc.h
// Copyright (c) 10/05/2021 by BCCSkelton
//////////////////////////////////////////

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

(解説:ダイアログの初期設定です。ラジオボックスとファイルを開くボタンを設定します。)
    //暗号化ラジオボタンにチェックを入れる
    SendItemMsg(IDC_RBCRYPT, BM_SETCHECK, BST_CHECKED, 0);
(解説:BM_SETCHECKメッセージでチェックを入れます。(外す場合はBST_UNCHECKED))

    //IDI_ICONアイコンをIDC_SELECTボタンに張り付ける
    HICON hIcon = (HICON)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
(解説:まずリソースのアイコンをロードし、ハンドルを取得します。)

    SendItemMsg(IDC_SELECT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);

(解説:そのハンドルを使ってBM_SETIMAGEで貼り付けます。)
    return TRUE;
}

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

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

    PostQuitMessage(0);
    return TRUE;
}

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

    char* cp = cmndlg.GetFileName(m_hWnd, "全てのファイル(*.*)\0*.*\0\0", TRUE);
    if(cp) {
        SendItemMsg(IDC_FILE, WM_SETTEXT, 0, (LPARAM)cp);
        return TRUE;
    }
    else
        return FALSE;
}
(解説:文字列ポインターcpを使ってCMNDLGクラス変数でファイルパス、名を取得してエディットコントロールに表示します。)
 

bool CMyWnd::OnCrypt() {

    //変数定義
    char fn[MAX_PATH];        //ファイル名用バッファ
    char* dat;                //ファイルデータ読み込み用
    bool bSuccess = FALSE;    //処理結果フラグ

(解説:成功した場合のみTRUEとし、それ以外は規定値FALSEを使います。)
    DWORD dwRead;            //読み込んだファイルサイズ
    //選択ファイル名の取得
    SendItemMsg(IDC_FILE, WM_GETTEXT, MAX_PATH, (LPARAM)fn);
(解説:まずはfn配列変数にファイルパス、名を取り込みます。)

    //選択ファイルの読み込み
    HANDLE hFile = CreateFile(fn, GENERIC_READ, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
    if(hFile == INVALID_HANDLE_VALUE)            //オープンできなかった場合
        MessageBox(m_hWnd, "ファイルを開けませんでした", "エラー",
                    MB_OK | MB_ICONERROR);
(解説:ここですぐにreturnしないで、次のelse処理を合わせてFALSEならreturnさせます。)

    else{
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize == 0xFFFFFFFF) {            //-1は(0xFFFFFFFF)エラー
            MessageBox(m_hWnd, "ファイルサイズを取得できませんでした", "エラー",
                        MB_OK | MB_ICONERROR);
            CloseHandle(hFile);                    //ファイルのクローズ

(解説:ここでも一括FALSE処理に任せますが、ファイルを開いているので閉じましょう。)
        }
        else {
            dat = new char[dwFileSize];    //データバッファを確保する
            if(ReadFile(hFile, (LPVOID)dat, dwFileSize, &dwRead, NULL))
                bSuccess = TRUE;

(解説:唯一読み込みに成功したときだけTRUEにします。)
            else {
                MessageBox(m_hWnd, "ファイルを読めませんでした", "エラー",
                            MB_OK | MB_ICONERROR);
                delete [] dat;    //バッファの開放

(解説:ここも一括FALSE処理ですが、newでメモリーを確保したので、忘れずに解放します。)
            }
        }
        CloseHandle(hFile);                        //ファイルのクローズ
    }
    //ファイル読み込みの結論とbSuccesの再初期化
    if(!bSuccess)
        return FALSE;

(解説:これが一括FALSE処理です。)
    else
        bSuccess = FALSE;

(解説:読み込めた時にはまたFALSEの規定値に戻します。)
    //暗号化、復号化の開始
    DATA_BLOB blobIn;
    blobIn.cbData = dwRead;            //暗号化するデータのサイズ
    blobIn.pbData = (BYTE*)dat;        //暗号化するデータのバイトポインター
(解説:読み込んだ暗号化/復号化ファイルデータをセットします。)

    DATA_BLOB blobOut;
    CRYPTPROTECT_PROMPTSTRUCT promptStruct;
    promptStruct.cbSize = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
    promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
    promptStruct.hwndApp = m_hWnd;

(解説:これがDPAPI専用ダイアログ用の構造体です。構造体サイズ、プロンプトは定番の暗号化時、親はメインダイアログとします。)
    //暗号化、復号化の確認
    if(SendItemMsg(IDC_RBCRYPT, BM_GETCHECK, 0, 0) == BST_CHECKED) {

(解説:「暗号化」ラジオボックスのチェックを確認し、TRUEなら暗号化、FALSEなら復号化処理を行います。)
        promptStruct.szPrompt = L"データの暗号化";

(解説:文字列の前のLは、wchar_tのマクロでワイド文字であることを示します。)
        if(CryptProtectData(&blobIn, NULL, NULL, NULL, &promptStruct, 0, &blobOut)) {
            MessageBox(m_hWnd, "ファイルを暗号化しました", "暗号化", MB_OK | MB_ICONEXCLAMATION);
            bSuccess = TRUE;

(解説:暗号化に成功したときだけTRUEにします。)
 

        }
        else
            MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), "エラー", MB_OK | MB_ICONWARNING);
    }
    else {
        promptStruct.szPrompt = L"データの復号化";
        if(CryptUnprotectData(&blobIn, NULL, NULL, NULL, &promptStruct, 0, &blobOut)) {
              MessageBox(m_hWnd, "ファイルを復号化しました", "復号化", MB_OK | MB_ICONEXCLAMATION);
            bSuccess = TRUE;
(解説:復号化に成功したときだけTRUEにします。)
        }
        else
            MessageBox(NULL, TEXT("データの復号化に失敗しました。"), "エラー", MB_OK | MB_ICONWARNING);
    
    }
    delete [] dat;    //もう必要が無いのでバッファを解放
    //暗号化復号化の結論とbSuccesの再初期化
    if(!bSuccess)
            return FALSE;
(解説:一括FALSE処理です。)
    else
        bSuccess = FALSE;

(解説:TRUEを、再度規定値FALSEにします。)
    //ファイルを書込みでオープン
    DWORD dwWritten;                //読み込んだファイルサイズ
    hFile = CreateFile(fn, GENERIC_WRITE, NULL, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE) {    //ファイルオープン成功
        DWORD dwWritten;            //読み込んだファイルサイズ
        //{blobOut.pbDataが暗号化されたデータ、blobOut.cbDataはそのサイズ
        bSuccess = WriteFile(hFile, blobOut.pbData, blobOut.cbData, &dwWritten, NULL);
(解説:bSuccessに書き込み結果の成功TRUE、失敗FALSE情報が入ります。)

        CloseHandle(hFile);            //ファイルのクローズ

(解説:開いたファイルは閉じます。)
    }
    LocalFree(blobOut.pbData);        //blobOutのメモリーを解放

(解説:暗号化、復号化処理で確保したメモリーを解放します。)
    return bSuccess;    //書き込みに成功している場合はTRUE、それ以外はFALSE
}

bool CMyWnd::OnIdok() {

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

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////

 

以上でプログラミングを終了し、ビルドすると

 

 

というプログラムになります。

 

使い方は簡単です。まず「DPAPIフォールダー」ボタンを押してファイルを選択します。次に暗号化ラジオボタンにチェックが入っていることを確認して「実行」ボタンを押します。

 

 

するとDPAPI専用のダイアログが現れます。

 

 

このまま「OK」しても暗号化されますが、「セキュリティレベルの設定」を押して現れるダイアログで「高」を選ぶとパスワードを設定できます。

 

 

最初の「~のパスワード」の入力は「そのパスワードのID」です。(英語では"Password of (エディットボックス)"だったのでしょう。)復号化の時にこのIDが表示されてパスワードを求められます。パスワード自体はその下の「パスワード」と「確認入力」にタイプインします。

暗号化に成功すると"ファイルを暗号化しました"というダイアログが現れますので、ファイルをチェックして暗号化されていることを確認してください。

 

 

暗号化されたファイルは同じ様に復号化ラジオボタンにチェックが入っていることを確認して「実行」ボタンを押すことで復号化します。"ファイルを復号化しました"というダイアログが現れたら、もう一度ファイルが元に戻っているか確認してください。

 

では、今度はまたIDListに戻って最後の仕上げをしましょう。

 

いつもながら、簡単なResDPAPIとDPAPI.cppを紹介しますが、今回は簡単なのでDPAPI.hについても解説します。

 

【ResDPAPI.h】

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_MAIN
#define    IDC_SELECT        100
#define    IDC_RBCRYPT        101
#define    IDC_RBDECRYPT        102
#define    IDC_CRYPT        103
#define    IDC_FILE        104
#define    IDC_GROUPBOX        105

//---------------------
//  メニューリソース
//---------------------

//---------------------
//  イメージリソース
//---------------------
#define    IDI_ICON        200

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

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

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

 

(解説:単なる定数定義ファイルなのでコメントはありません。)

 

【DPAPI.cpp】

//////////////////////////////////////////
// DPAPI.cpp
//Copyright (c) 10/05/2021 by BCCSkelton
//////////////////////////////////////////
#include    "DPAPI.h"
#include    "DPAPIProc.h"

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

    //2重起動防止(解説:以下5行のコメントを外し、次の一行削除)
    if(!DPAPI.IsOnlyOne()) {
        HWND hWnd = FindWindow("MainWnd", "DPAPI");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!DPAPI.Create(NULL, hInstance, "IDD_MAIN", ModelessProc))
        return 0L;

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

(解説:これもSkeltonWizardファイルのコメントを外した程度で特記するところはありません。)

 

【DPAPI.h】

//////////////////////////////////////////
// DPAPI.h
// Copyright (c) 10/05/2021 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResDPAPI.h"
/* オブジェクト ファイル内にライブラリ検索レコードを配置します。
   このコメントの種類には、リンカーで検索するライブラリの名前 
   (および場合によってはパス) を持つコメント文字列パラメーターを
   指定する必要があります。
*/
#pragma comment (lib, "crypt32.lib")
(解説:この"#progma comment"の解説をコメントで追加していますが、この一行を外してコンパイルするとDPAPIの暗号化、復号化関数が見当たらないというエラーになります。実際にコメントアウトしてコンパイルしてみてください。要すれば「このライブラリーを探してリンクしてください。」という意味です。)


/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}
    //メニュー項目、ダイアログコントロール関連
    bool OnSelect();
    bool OnCrypt();
    bool OnIdok();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);
};

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

BEGIN_MODELESSDLGMSG(ModelessProc, DPAPI)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(DPAPI, IDC_SELECT, OnSelect())
    ON_COMMAND(DPAPI, IDC_CRYPT, OnCrypt())
    ON_COMMAND(DPAPI, IDOK, OnIdok())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_DESTROY(DPAPI)
END_DLGMSG
(解説:他のコントロールの関数は削除します。)
 

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

(解説:「ファイルを開く」ダイアログを使うので入れています。)

 

今回はSkeltonWizardが作ってくれたものにほとんど手を入れていないので解説も少ないですね。次回はDPAPIProc.hとDPAPIの実行について書きます。