と、いうことで、今回はゲームを作ります。プロジェクトは"LightCycle"。この由来詳細は前回ブログをご覧ください。なお、プログラムは発展が分かるように時系列の変化を記しますが、途中に仕様変更があるので、そのタイムラグはブログでコメントします。

 

進め方は、以下の通りとします。

(1)大雑把な仕様を作る(「将来の仕様変更を恐れない!」ということが大事です。今回は結構仕様変更、書き直しがありました。)

(2)動作試験をするため、プログラムのスケルトンを作成する。(これはBCCForm and BCCSkeltonのおかげで簡単、簡単。)

(3)LightCycleをクラス定義する。(現実のプログラミングでは、特にグラフィック処理に関して、プログラムのウィンドウクラスとこのクラスがそれぞれどこまで処理するのかの線引きが難しかったです。)

(4)動作確認、プログラム修正(仕様変更を含む)の繰り返し。

(5)一応の完成と完成動作試験。

(6)改良。

 

仕様は次の通りとします。

(1)ウィンドウはSDIで、メニュー、ツールバー、ステータスバー、仮想ウィンドウ、コモンダイアログを備えます。

(2)リソースはLightcycleのアイコン(EZImageで作成32x32と16x16)が一つ。

メニューはこんな感じ。

ツールバービットマップも作ります。

その他、ダイアログはバージョンダイアログの他にLightCycleの属性情報入力用(これは仕様詳細が煮詰まった後に考えることとする)の二つが最低限必要です。
 

(3)(一番大事な)LghtCycleクラスについては、実際にクラス定義を作ってゆくときの備忘用コメントを載せます。

///////////////////////////////
//CLIGHTCYCLEクラス定義ファイル
///////////////////////////////
///////////////////////////////【仕様】///////////////////////////////
//【識別色】(m_Color)
//LightCycle車両は識別色を持つ。識別色はユーザーが決定する。(注:これはCANVASクラスの色コードから最終的に8-15となった。)
//【方向】(m_Dir)(注:以下のm_x、m_yなど動作で変化する変数は、後に「最初の値」と「動作で変化する値」の二つの変数が必要になることが判明。)
//LightCycleでは方向を0-7で表す。
//    0                //y--
//  7 ↑ 1            //y--, x++(x--)
//6←    →2        //x++(x--)
// 5 ↓ 3            //y++, x++(x--)
//    4                //y++
//【位置関係】
//LightCycleの現在位置は2次元(平面)上のx(m_x)y(m_y)座標で特定する。
//走行範囲は競技場限界(m_MinX/-x座標最小値、m_MinY-y座標最小値、
//m_MaxX-x座標最大値、m_MaxY-y座標最大値)内とする。
//また、現在の方向と位置に基づく移動位置をm_nx、m_nyとする。(注:次の移動位置または移動予定位置)
//競技の勝敗決定因である移動時間(プログラム的にはコールされた回数)
//はm_Timesとする。(要すれば一番長く生き残った者が勝つ。)
//【方向転換】
//方向転換のルールとして、進行方向の左右二つずつまでを許容
//    0                //m_Dir == 0であれば
//  7 ↑ 1            //左右斜め上と
//6←    →2        //左右の転回を許す。(注:この段階では斜め線をまたぐ移動は許可しようとしていた。)
//また、競技車に一定間隔で方向を自ら変えることを許す。プ
//ログラム上は一定の動作回数で方向を変換させる。(m_Caprice)
//if(!m_Caprice || (m_Times % m_Caprice)) return FALSE;(注:この時点の仕様。「0なら実行しない、または0でなければ割り切れなければ実行しない。」という意味。)
//【速度】(m_Delay)
//競技車の速度は可変とする。プログラム上はコールされる動作
//回数をm_Delayで除した余りが0の場合実行させる 。
//if(m_Delay && (m_Times % m_Delay)) return FALSE;(注:この時点の仕様。m_Delayがウェイトの意味を持つ。値を持てば剰余がゼロの場合以外は処理を行わない、という意味。)
//////////////////////////////////////////////////////////////////////

 

その他競技場をどうするか、LightCycleの特性付与をどうするか、動作コントロールはどうするかetcありますが、まぁ、キックオフ時点の仕様としてはこんなところでまずは書いて動かそうじゃないか、といい加減にプログラミングを開始します。

 

家庭内でコンピューター処理する業務なんてあんまりないので、最後はアミューズメントになってくるのは仕方がありません。ということで、そろそろ(私が苦手の)ゲームをやってみようかと思います。

 

世の中には多くのコンピューターゲームがあります。

私が最初にコンピューターゲームに触れたのは1970年代のアーケードゲームで、インベーダー、ブロック崩し、テニスなどというゲーム(ゲームプログラミングの入門でよくやりますね)あたりから、ギャラクシアン、ドンキーコング等、当時でもこんなにあるのですね。その後1980年代に入り、家庭用ゲーム機が開発されるようになり、スーパーマリオ、ドラゴンクエストやFF等任天堂の天下が訪れます。1990年代はPCゲームも盛んで、中盤以降はDiabloのようなネットワークRPGが開花しました。その間にゲーム機、PCのハード性能が上がり、今やゲーム機の画面は実写写真を見ているようです。

 

じゃ、極限までリアルだと面白いのか?

 

その話になると、20年ほど前に仕事で任天堂の役員の方からいただいた話をおもいだします。曰く、任天堂はPSとハード競争をするつもりはなく、(プレーヤーが参加する)ゲームとしての面白さを追求する、というものでした。実際、コントローラーをラケットやゴルフクラブに見立てる発想などはかなり受けた様ですね。いずれにしても今後もアミューズメント産業の市場規模は拡大し、ハード的にも、ソフト的にも進化してゆくことでしょう。

 

では、アマチュアプログラマーが作るゲームの面白さって何でしょうか?我々は高細精度のハードを操ったり、膨大な画像データを作ることも無理です。動画スピードも限界があります。RPGなどのストーリー性も産業の開発陣には及ばないと思いますし、「要すれば、余り高望みしない。一回以上やりたくなれば大成功!」というのが現実的なゴールでしょう。

 

では、もう一回やりたがるゲームとはどんなものでしょうか?

(1)負けると悔しく感じるゲーム

(2)毎回少し内容が変化して、飽きさせないゲーム

(3)何よりも、勝ったときの達成感があるゲーム

等が最低限の要素でしょう。(とはいえ、いずれも結構ハードルが高いですね。)

 

先だっての「昔話」の際に、当時使っていた8 BitのMZ-2500というPCを紹介しましたが、それ用に"LightCycle"というプログラムが雑誌(注)載ったことを思い出しました。

注:MZマガジン。当時は(NECの)PCマガジン、(富士通の)FMマガジンなんてありましたね。

これは1982年の映画「Tron」に出てくる電脳世界の二輪車でそのオマージュライドが上海にありますね。(こんな風に乗るのですね。)

 

これは当時の640X400の画面の中に縦長に240X300程度の競技場を設け、その4隅にLightCycleを置いてスタートされ、その走行痕(各車の色が直線であらわされる)に挟まれて行き場を失うと負けで、最後まで生き残った競技車が勝ち、というゲームでした。しかし、当時の環境では方向性が「直進と左右転回」、「毎回同じ走行経緯」になり、プログラムを打ち込んで1回目は「やったー」、二回目は「ん?前と同じ動きと結果だ」で「やめやめ」になるか、「乱数を入れて時々展開させるようにしよう」となるくらいでした。(因みに私は後者でした。)

 

同じプログラムを今書くのもなんだし、この4台(最大)の走行特徴を変更でき、毎回異なる特性の競技車を競わせて遊ぶことができれば、上記3要素を結構満たす(特に競技者の特性をどう設定するか、がプレーヤーの考えるところなので)のではないか、と考え始めました。

 

まぁ、上手くいくかどうかわからないけど、とりあえずこの"LightCycle"プロジェクトを始めてみましょう。(注)

注:というのが、2週間前の気持ちでした。その後、スケルトンで競技場を作り(これはBCCForm and BCCSkeltonで簡単)、次にゲーム進行をタイマーでやるか、別スレッドでやるか検討し、最後に"LightCycle"をクラスとしてカプシュール化してゆきますが、ところどころ課題にぶち当たり、仕様変更を繰り返し、現在も悪戦苦闘中、というのが実際です。今後はゆっくりとした頻度(その間にまた与太話をブログで入れますが)で公開してゆきたいと思います。

 

実用プログラムのIDListと(あまりにも手抜きだった)開発ツールとしてのDumpの改良が終わったので、しばらく「次は何か」を考える間、駄文を書かせていただきます。

 

既に私のプログラミングヒストリーは「【昔話】私の〇ビット時代」で書かせていただきましたが、プログラミングへのアプローチは(昔の学級教科と同じように)最初はプログラミング言語の文法から入り、次にお手本としてサンプルプログラムを真似して打ち込み、自分で考えたプログラムを実際に作ってみて、自分なりのプログラミング作法ができてくる、という旧い昭和風の考え方(注)でした。

注:「やってみせ、言って聞かせて、させてみせ、ほめてやらねば、人は動かじ。話し合い、耳を傾け、承認し、任せてやらねば、人は育たず。やっている、姿を感謝で見守って、信頼せねば、人は実らず。」(山本五十六海軍元師)

 

そんなアプローチを支える開発環境は、年代順の一覧にすると次のようになります。(以下NY = New York、SP = Singapore)

【8 Bit】IOCS + Basic-M25、Assembler、CP/M + BDS-C(会社ではMS-DOSのみで開発できず)

【16 Bit】MS-DOS  + MS QuickBasic、Borland Turbo C++、 Windows 3.x + Borland C++ 1.0 (在NY-会社ではdBASE IVで開発)

【32 Bit】Windows 95→Windows XP、ActiveBasic、BorlandC++ 5.5 (在SP-BCCForm and BCCSkelton)

     日本に帰国する直前にBorlandC++ Builder 5.0(英語版)を入手、また帰国後MSのC++、Visual Basicを入手。

     日本で雑誌の付録に乗っていたDelphi 5.5を入手

     Windows 10 + ActiveBasic、BorlandC++ 5.5 (2020年から)

 

プログラミングを始めた8 Bitの頃はCUIで、エディターすらまともにない状態でしたが、楽しくて結構根を詰めて、長時間Basic-M25とBDS-Cでプログラミングしましたね。(当時は「構造化プログラミング」が「ナウい(汗;)」スタイルでした。)

 

NYへ行ってMS QuickBasic、Borland Turbo C++に触れ、MS-DOSのキャラクターベースでしたが、(もともと初代白黒AppleのWYSWYG環境にあこがれていたこともあり-注)GUIっぽい統合環境に感服しました。しかし仕事でデータベースを開発するのに忙しく、学習の為にDOSベースのサンプルを作るくらいのプログラミングしかしませんでした。また、BorlandがウィンドウベースのC++(Version 1.0)統合環境(Borland C++ Workspace -BCW)を出したので早速購入し、英語のマニュアルのみならず、日本出張の際に日本語のプログラミング関係書籍も買いましたが、(割り込みOSとしてのウィンドウズの知識がなく)ウィンドウズプログラミング用のへんてこなマクロを使ったプログラムスケルトンは全く理解不能で、すぐにポイっと投げ出しちゃいました。(それがBCCSkeltonの原型になっているから皮肉です。)

注:MSXを使っていた私が初代白黒AppleのWYSWYG環境に最初に触れたのは、1985年に米国へ短期留学した際のUC Berkleyにおいてでした。しかし、どういう訳かアップルは過去入手したことも、欲しいと思ったこともなく、開発はすべてMS-DOSとWindowsでしたね。

 

本格的にネィティブコンパイラーでWindowsプログラミングにはまったのは、SPでフリーのBCC 5.5をダウンロードしてからですが、当時はBCCの開発環境が全くありませんでした。そんな中でフリープログラムのBCC Develperに出会い、開発環境の飛躍的な改善ができました。しかし、ウィンドウズプログラムを書くのにフリーのリソースエディターが無いことに発奮し、BCCFormとその周辺ツール、およびBCCSkeltonとSkeltonWizardを開発してゆきました。ということで「開発するのは開発ツールだけ」というのが私のプログラミングの特徴ですね。

 

実はSPで出張した帰りのチャンギ空港でなんとBorland C++ Builder 5.0(英語版)のCDを入手します。すぐにインストールしてVisual BasicばりのWYSWYG環境を試します。


(写真は現在のWin10環境で起動した状態)

 

様々なコントロール、パッケージやActiveXなどをコンポーネントとして貼り付けられ、それらの属性はObject Inspectorで指定でき、細部はコードを編集し、デバッガーやイメージツールなども付いていたのですが、(つまらないプログラムでも巨大なランタイムが必要という弱点もあり)何故かほとんど使いませんでした。やはり「GUI開発環境で何か作るより、GUI開発環境を作りたい」のが夢だったのでしょう。

 

また、SPから帰国後Visual Basicの開発環境が欲しいなと思い、MicrosoftのVisual Studioも入手しましたが、C++はMFCでなじめず、共に碌に使いもしないでいじって遊んでいるだけでしたね。また、BCBとDelphiの互換性からDelphi 6のCDが付いたプログラミング本(注)を買っ足りもしましたが、「内部コードが同じでも、覚えることが増えるだけ」とお蔵入りにしました。
注:実はBorland C++のフリー開発環境であるBCC DeveloperはDelphiで開発されました。また、このDelphiの本にあるサンプルプログラムは2020年に見様見真似でBCB 5でC++に移植しました。案外と簡単でしたね。

 

そんなこんなで、15年ほどすっかりプログラミングからは遠ざかりました。

 

2020年に定年退職してプログラミングもボケ防止に再度始めよう、ということで選んだのは、Borland C++ Builderの承継会社であるEmbacadero社のC++ Builder Community Editionでした。しかしダウンロードして立ち上げると、この20年の発展と変化に呆然とし、「もうPCではなくモバイルデバイスの時代だな。C++ではなくC#、いやそれよりもRubyやPaython、いやその前にXMLか」「余生をかけても、もう全部はわからないだろうな」という感想です。それでも多彩、豊富なプラットフォームやコントロールを試しているうちに少しづつ昔の感が戻ってきました。しかし、ビルド時にメモリー管理上のエラーに見舞われるようになり、webで調べた対処法で一旦は回避したものの、再度発生した際に「もう無理(とうか、使用許諾期間の1年も終わりに近かったし)」ということで、あっさりと手を切りました。(その間にBCCForm and BCCSkeltonを使ってCOMやShellを使ったプログラムも開発を終えていたので。)

 

そんな、こんなで未だにC++でプログラミングをしています。恐らく私にはActive Basicだろうが、(新たに習う)他の言語だろうが、あまり関係が無いのだと思います。何かを作りだすのが単に楽しいので今でも使い慣れた「20年前の開発環境」で遊んでいる、ということでしょう。

今回はDump改良の最終回で、DumpProc.hを「(解説:)」で解説します。が、その前に一点Dump.hファイルに追加を行います。

 

【Dump.h】

//////////////////////////////////////////
// Dump.h
// Copyright (c) 03/04/2021 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
<以下略>


/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}
    //メンバー変数
    char* m_FileName;            //ファイル名
    CFILE m_File;                //ファイルクラス変数
    CSTR m_Buff;                //エディットコントロール用文字列バッファ
    int m_LastPage;                //最大ページ数
    int m_Page;                    //表示ページ数
    HBITMAP m_hBmp[4];            //ページ移動ボタン用ビットマップ
    //メニュー項目、ダイアログコントロール関連
<以下略>

ページ移動用のプッシュボタンに"←"や"→"などを入れてもよいのですが、ビットマップを張ることになっていましたので、4つのビットマップハンドルをDumpのメンバー関数として持たせましょう。

ではDumpProcに進みます。

 

【DumpProc.h】

/////////////////////////////////////////
// DumpProc.h
// Copyright (c) 03/04/2021 by BCCSkelton
//////////////////////////////////////////

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

    //コモンコントロールの初期化
    InitCommonControls();
//(解説:今回使うツールヒントコントロールには不可欠ですが、SkeltonWizardが既に記述しています。)
 

    //ステータスバー登録-SetHandle(hWnd))
    SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[1] = {-1};
    SBar.SetSection(1, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "ファイル名-「ファイル」ボタンで選択");
//(解説:今回ステータスバーの枠は一つで、ファイルパス、名を表示します。初期値はガイダンス表示です。)

    //ビットマップをボタンに張り付ける
    m_hBmp[0] = (HBITMAP)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_TOP), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
    SendItemMsg(IDC_TOP, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)m_hBmp[0]);
    m_hBmp[1] = (HBITMAP)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_BEFORE), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
    SendItemMsg(IDC_BEFORE, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)m_hBmp[1]);
    m_hBmp[2] = (HBITMAP)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_NEXT), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
    SendItemMsg(IDC_NEXT, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)m_hBmp[2]);
    m_hBmp[3] = (HBITMAP)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_BOTTOM), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
    SendItemMsg(IDC_BOTTOM, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)m_hBmp[3]);
//(解説:プッシュボタンにビットマップを貼るには、プッシュボタンのウィンドウスタイルにBS_BITMAPを加え、WM_CREATEまたは今回のようにWM_INITDIALOGでリソースのビットマップをロードし、BM_SETIMAGEとIMAGE_BITMAPで貼り付けます。アイコンを使う場合にはLoadImageとBM_SETIMAGEでIMAGE_ICONを使います。)
 

    //IDC_TOP、IDC_BEFOREとIDC_NEXT、IDC_BOTTOMボタンを無効にする
    ChangeTopStatus(FALSE);
    ChangeBottomStatus(FALSE);    

//(解説:Change~Status関数はIDC_TOPとIDC_BEFORE、IDC_NEXTとIDC_BOTTOMを有効、無効にするユーザー定義関数です。後で解説します。)

    //IDC_PAGEの文字数を6(5桁)に設定
    SendItemMsg(IDC_PAGE, EM_LIMITTEXT, 6, 0);
//(解説:ページ移動ボタンの中央にページ表示R/Oエディットボックスを配置しますが、この桁数を5文字(およびNULL終端で6)に設定しています。)

    //ドラッグアンドドロップの受付開始
    DragAcceptFiles(m_hWnd, TRUE);
//(解説:前にもやりましたが、ファイルのドラッグアンドドロップを開始する際の定番関数です。)

    //ボタンとエディットボックスにツールチップを付ける
    if(!SetToolTip(m_hWnd, IDC_FILE, "Dumpするファイルを選択します"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_FILE)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDC_EDIT, "Dumpデータ表示エリア"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_EDIT)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDC_TOP, "ファイルの先頭に行きます"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_TOP)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDC_BEFORE, "1ページ戻ります"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_BEFORE)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDC_PAGE, "中央のページです"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_PAGE)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDC_NEXT, "1ページ進みます"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_NEXT)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDC_BOTTOM, "ファイルの後尾に行きます"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDC_BOTTOM)", "エラー", MB_OK | MB_ICONERROR);
    if(!SetToolTip(m_hWnd, IDOK, "終了します"))
        MessageBox(m_hWnd, "ツールチップを付けられませんでした(IDOK)", "エラー", MB_OK | MB_ICONERROR);
//(解説:Dumpのダイアログに使った8つのコントロール全て(デモンストレーションなので-汗;)について、Dump.cppの#includeで取り入れているToolTip.hに書いたSetToolTip関数を使ってツールチップを付与しています。)

    //ファイルドロップによる起動
    if(g_ByFile)
        OnFile();
//(解説:プログラム起動の際に引数としてファイルパスと名が与えられていれば、g_ByFileフラグがTRUEなので「ファイルを開く」のOnFile関数に飛びます。)

    return TRUE;
}

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

    SBar.AutoSize();
    return TRUE;
}
//(解説:Dumpダイアログはサイズ変更ができないので不要のように思えますが、最初にダイアログが現れるときに呼ばれるので、その際にステータスバーを調整してクライアントエリア幅いっぱいに伸ばします。)

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

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES) {
        //ボタンのビットマップを解放する
        DeleteObject(m_hBmp[0]);
        DeleteObject(m_hBmp[1]);
        DeleteObject(m_hBmp[2]);
        DeleteObject(m_hBmp[3]);
//(解説:プッシュボタンに張り付けたビットマップはメモリー割り当てを行われているので、使い終わったら解放します。)

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

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

    PostQuitMessage(0);
    return TRUE;
}

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

    //典型的なドラッグアンドドロップ処理
    static char lpszFn[MAX_PATH];
    HDROP hDrop = (HDROP)wParam;                     //HDROPを取得
    DragQueryFile(hDrop, 0, lpszFn, MAX_PATH);        //ファイル名を取得
    m_FileName = lpszFn;
    g_ByFile = TRUE;
    OnFile();
    DragFinish(hDrop);                                //終了処理
    return TRUE;
}
//(解説:これも前に書きましたが定番処理です。m_FileNameにファイルパス、名をセットし、g_ByFileフラグを使ってOnFile()を読んでいます。)

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

    //ファイル選択(ドラッグアンドドロップでは省略)
    if(g_ByFile)
        g_ByFile = FALSE;
//(解説:g_ByFileフラグが真ならm_FileNameに既にデータがある、ということです。使い終わったら初期値のFALSEに戻します。)

    else
        m_FileName = cmndlg.GetFileName(m_hWnd, "すべてのファイル(*.*)\0*.*\0\0", TRUE);
//(解説:g_ByFleがFALSEなら、ファイルを開くダイアログを使ってm_FileNameにファイルパス、名をセットします。)

    if(!m_FileName) {
        MessageBox(m_hWnd, "ファイル選択がキャンセルされました", "キャンセル", MB_OK | MB_ICONSTOP);
        return FALSE;
    }
    //ステータスバーにファイル名表示
    SBar.SetText(0, m_FileName);
    SBar.SendMsg(SB_SETTIPTEXT, 0, (LPARAM)m_FileName);    //ToolTipをつける

//(解説:ステータスバーに取得したファイルパス、名を表示します。また長くてすべて表示できない場合にはツールチップが出るようにします。)
    //ファイル読み込みと初期化処理
    m_File.LoadFile(m_FileName);                        //ファイルを読み込む
    m_File.GoTop();                                        //ファイルの先頭へ
    m_Page = 0;                                            //最初のページ
    m_LastPage = m_File.m_FileSize / PAGE;                //Fileバイト数をPAGE単位とする
    //最初のページを表示する(解説:内部的にm_Pageは0~ページ数 - 1、IDC_PAGEの表示はこれに+1されます。)
    SetPage(m_Page);
    //最初のデータの表示(解説:現在のページ-m_Page-のデータが表示されます。)
    ShowData(m_Page);
    //最初のボタン状態の設定
    ChangeTopStatus(FALSE);
    ChangeBottomStatus(TRUE);
//(解説:コメントにある通りのシーケンスで処理を行います。なお、次へと最後へのボタンは有効化されますが、1ページしかない場合、押した段階で無効化されます。)

    return TRUE;
}

bool CMyWnd::OnTop() {

    m_Page = 0;            //最初のページ
    SetPage(m_Page);    //ページを表示する
    ShowData(m_Page);    //データを表示する
    //IDC_TOPとIDC_BEFOREボタンを無効にする
    ChangeTopStatus(FALSE);
    //IDC_NEXTとIDC_BOTTOMボタン
    if(m_LastPage)    //1ページだけでなければ有効にする
        //IDC_NEXTとIDC_BOTTOMボタン状態を変更する
        ChangeBottomStatus(TRUE);
    else             //1ページだけならボタンを無効にする
        ChangeBottomStatus(FALSE);
//(解説:0ページ(表示は「1」)に飛ぶ処理です。現在ページの保存、その表示、当該ページのデータ表示、ボタンの有効・無効処理を行います。)

    return TRUE;

}

bool CMyWnd::OnBefore() {
                            
    if(!m_Page) {            //既に最初のページであれば
        //IDC_TOPとIDC_BEFOREボタンを無効にする
        ChangeTopStatus(FALSE);
        return FALSE;         //何もしないで戻る
    }
    else {
        m_Page--;            //そうでなければページを一つ戻し
        SetPage(m_Page);    //ページを表示する
         ShowData(m_Page);    //データを表示する
        //IDC_NEXTとIDC_BOTTOMボタンを有効にする
        ChangeBottomStatus(TRUE);
        if(!m_Page)            //最初のページであれば
        //IDC_TOPとIDC_BEFOREボタンを無効にする
        ChangeTopStatus(FALSE);
        return TRUE;
    }
}
//(解説:1ページ戻る処理です。現在ページの保存、その表示、当該ページのデータ表示、ボタンの有効・無効処理を行います。)

bool CMyWnd::OnNext() {

    if(m_Page == m_LastPage) {    //既に最後のページであれば
        //IDC_NEXTとIDC_BOTTOMボタンを無効にする
        ChangeBottomStatus(FALSE);
        return FALSE;              //何もしないで戻る
    }
    else {
        m_Page++;            //そうでなければページを一つ進め
        SetPage(m_Page);    //ページを表示する
        ShowData(m_Page);     //データを表示する
        //IDC_TOPとIDC_BEFOREボタンを有効にする
        ChangeTopStatus(TRUE);
        if(m_Page == m_LastPage)    //最後のページになれば
            //IDC_NEXTとIDC_BOTTOMボタンを無効にする
            ChangeBottomStatus(FALSE);
        return TRUE;
    }
}
//(解説:1ページ進む処理です。現在ページの保存、その表示、当該ページのデータ表示、ボタンの有効・無効処理を行います。)

bool CMyWnd::OnBottom() {

    m_Page = m_LastPage;    //最後のページ
    SetPage(m_Page);        //ページを表示する
    ShowData(m_Page);        //データを表示する
    //IDC_NEXTとIDC_BOTTOMボタンを無効にする
    ChangeBottomStatus(FALSE);
    //IDC_TOPとIDC_BEFOREボタ
    if(m_Page)        //1ページだけでなければ有効にする
        //IDC_TOPとIDC_BEFOREボタン状態を変更する
        ChangeTopStatus(TRUE);
    else             //1ページだけならボタンを無効にする
        ChangeTopStatus(FALSE);

    return TRUE;
}
//(解説:最終ページに飛ぶ処理です。現在ページの保存、その表示、当該ページのデータ表示、ボタンの有効・無効処理を行います。)

bool CMyWnd::OnIdok() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
}
//(解説:IDOK(「終了」)ボタンによる終了処理です。)

//ユーザー定義関数
bool CMyWnd::SetPage(int page) {

    char num[6];                    //5桁まで対応可能
    wsprintf(num, "%d", page + 1);    //ページ数の文字化(1 → m_LastPage + 1)
    SendItemMsg(IDC_PAGE, WM_SETTEXT, 0, (LPARAM)num);    //IDC_PAGEへ表示
//(解説:ページ数のメンバー変数m_Page + 1の数をIDC_PAGEエディットコントロールに表示します。)
    return TRUE;
}

bool CMyWnd::ShowData(int page) {

//(解説:ページ(7168バイト)分のダンプデータを「アドレス+スペース+(2桁16進数字+スペース)x8バイト分+区切り:+(2桁16進数字+スペース)x8バイト分+区切り:+16バイトのキャラクター表示(制御文字は'.'表示)+'CRLF'」の行表示で出力します。
    char str[MAX_PATH];    //一行書き込み文字列バッファ
    m_Buff = "";    //初期化
    for(int i = page * PAGE; i < (page + 1) * PAGE; i += 16) {
        if(i >= m_File.m_FileSize)        //最終ページ対応
            break;
        int n = 0, j = 0;
        n = wsprintf(str, "%04X ", i);    //アドレス
        j += n;
        for(int k = 0; k < 8; k++) {    //8バイト16進書き込み
            n = wsprintf(str + j, "%02X ", m_File.m_Ptr[i + k] & 0xFF);
            j += n;
        }
        n = wsprintf(str + j, ":");        //セパレーター
        j += n;
        for(int k = 8; k < 16; k++) {    //8バイト16進書き込み
            n = wsprintf(str + j, "%02X ", m_File.m_Ptr[i + k] & 0xFF);
            j += n;
        }
        n = wsprintf(str + j, ":");        //セパレーター
        j += n;
        for(int k = 0; k < 16; k++) {    //キャラクター表示
            n = wsprintf(str + j, "%c", isalnum(m_File.m_Ptr[i + k]) ? m_File.m_Ptr[i + k] : '.');
            j += n;
        }
        n = wsprintf(str + j, "\r\n");
        m_Buff = m_Buff + str;
//(解説:書き終わった行データをCSTRクラス変数のm_Buffに付け足してゆきます。)

    }
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)m_Buff.ToChar());
//(解説:最後に出来上がったページデータをIDC_EDITコントロールに張り付けます。)
 

    return TRUE;
}

bool CMyWnd::ChangeTopStatus(bool stat) {

    //IDC_TOPとIDC_BEFOREボタン状態を変更する
    EnableWindow(GetDlgItem(m_hWnd, IDC_TOP), stat);
    EnableWindow(GetDlgItem(m_hWnd, IDC_BEFORE), stat);
    return TRUE;
}
//(解説:statに応じてプッシュボタンを有効、無効に変更します。)

bool CMyWnd::ChangeBottomStatus(bool stat) {

    //IDC_NEXTとIDC_BOTTOMボタン状態を変更する
    EnableWindow(GetDlgItem(m_hWnd, IDC_NEXT), stat);
    EnableWindow(GetDlgItem(m_hWnd, IDC_BOTTOM), stat);
    return TRUE;
}
//(解説:同上)

bool CMyWnd::GoMiddle(WPARAM wParam) {
//(解説:これは真ん中のIDC_PAGEコントロールにフォーカスが移った場合(EN_SETFOCUS)の処理です。エディットコントロールはWM_NOTIFYではなく、WM_COMMANDで通知が来る点に注意が必要です。通知メッセージはHIWORD(wParam)に入っています。 処理はページ移動ボタンと同様です。コメントをご覧ください。)

    //ファイルが選択されていない場合
    if(!m_FileName)
        return FALSE;
    //IDC_PAGEエディットコントロールにフォーカスがない場合
    if(HIWORD(wParam) != EN_SETFOCUS)
        return FALSE;
    //IDC_PAGEエディットコントロールにフォーカスが移った場合
    else
        m_Page = m_LastPage / 2;    //ページ中間へ進む
    if(m_Page == m_LastPage)        //m_LastPageが0なら
        ChangeBottomStatus(FALSE);    //IDC_NEXTとIDC_BOTTOMボタン無効化
    else {
        ChangeBottomStatus(TRUE);    //IDC_NEXTとIDC_BOTTOMボタン有効化
        if(m_Page)                    //最初のページでなければ
            ChangeTopStatus(TRUE);    //IDC_TOPとIDC_BEFOREボタン有効化
        else                        //最初のページなら
            ChangeTopStatus(FALSE);    //IDC_TOPとIDC_BEFOREボタン無効化
    }
    SetPage(m_Page);                //ページを表示する
    ShowData(m_Page);                 //データを表示する

    return TRUE;
}

 

出来上がったらBCC DeveloperかBCCMakerでビルドしてみましょう。以下は私の環境での動作像です。以下のようにファイル名が長すぎて全部表示されない場合、ステータスバーにツールチップが出るので確認してみてください。

 

Dumpの改良をしている際に、ダイアログコントロールの使い方を示せるようにしたいと考え、Win32のツールヒントコントロールに目をつけました。使い方等は以下コードのコメント通りで、余り付け加えることはありませんが、MSDNの説明にもかかわらず、私の環境ではTOOLINFO構造体のuFlagsTTF_IDISHWNDを入れないと表示しませんでした。また、それもツールヒントコントロールウィンドウを前に持って来ないと表示されませんでした。(何でかなぁ???)

現在はToolTip.hというファイルにして、Dumpの改良版でも#includeで取り込み、外部関数として使っています。クラスにすることも考えたのですが、これは一つ一つインスタンスで実現するオブジェクトというよりも機能なので、むしろBCCSkeltonのCCTRLクラスのメンバー関数に加えたりする方がベターでしょうね。

 

//////////////////////////////////////////////////////////////////////////
//ツールヒントコントロール(ツールチップ)付与関数
//【注意】
//アプリケーショの冒頭でInitcommoncontrols()関数を実行してください
//【概要】
//ツールチップをダイアログのコントロールに付ける
//【引数】
//hWnd        ダイアログ(ウィンドウでもよいが)のウィンドウハンドル
//CtrlID    コントロールID
//pszText    表示文字列
//【戻り値】
//ツールチップのウィンドウハンドル(作りっぱなしでよいみたいです)
//【参考】
//https://docs.microsoft.com/ja-jp/windows/win32/controls/tooltip-controls
//なお、TTF_IDISHWNDを外した、IDだけでは表示されなかった
//////////////////////////////////////////////////////////////////////////
HWND SetToolTip(HWND hWnd, int CtrlID, PTSTR pszText) {

    if(!CtrlID || !hWnd || !pszText) {
        return (HWND)NULL;
    }
    //対象コントロールのハンドルを取得する
    HWND hCtrl = GetDlgItem(hWnd, CtrlID);
    //文字列リソースのある親ウィンドウ(ダイアログ)のインスタンス
    HINSTANCE hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
    //ツールチップの作成
    HWND hTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
                    WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
                    CW_USEDEFAULT, CW_USEDEFAULT,
                    CW_USEDEFAULT, CW_USEDEFAULT,
                    hWnd, NULL, 
                    hInst, NULL);
    if(!hCtrl || !hTip) {
        return (HWND)NULL;
    }
    //ツールチップをコントロールに関連付ける
    TOOLINFO toolInfo = { 0 };    //ゼロクリアー
    toolInfo.cbSize = sizeof(toolInfo);
    toolInfo.hwnd = hWnd;
    toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
//    toolInfo.uFlags = TTF_SUBCLASS;
    toolInfo.uId = (UINT_PTR)hCtrl;
//    toolInfo.uId = (UINT_PTR)CtrlID;
    toolInfo.hinst = hInst;
    toolInfo.lpszText = pszText;
//    GetClientRect(hCtrl, &toolInfo.rect);    //もしuFlagsにTTF_IDISHWNDがあれば無視される
    SendMessage(hTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
    //私の環境では、表示する為に↓でツールヒントコントロールを全面に出す処理が必要だった

    SetWindowPos(hTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

    return hTip;
}
 

バグフィクスも完了したので、Dump改良を進めます。今回は定義関係を扱うDump.hです。いつものように(解説:)を付けて改良後のソースを示します。

 

//////////////////////////////////////////
// Dump.h
// Copyright (c) 03/04/2021 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResDump.h"
//1行16バイト表示に4('XXXX')+ 1(' ')+ 24('XX ')+ 1(':')+ 24('XX '+ 1(':')+ 16 = 71バイト必要
//エディットボックスの最大値
#define        EDITMAX    32767
//エディットボックスの最大文字数初期値は 32767バイト
//出力一行は4(アドレス)+ 1(スペース)+ 24(数字X2+スペース)+ 1(区切り:)
//+ 24(数字X2+スペース)+ 1(区切り:)+ 16(文字表示)+'CRLF'2 = 73バイトで
//448行迄出力可となることから、7,168バイトを読み出し単位とする。
#define        PAGE    7168
//(解説:)実は定数EDITMAXは改良では使っていません。もともとDumpProc.hにあった「1回の表示最大値」定義を引っ越し、残ったものです。削除していただいて構いません。逆に今回は「1回の表示値=PAGE」という概念とし、定義しています。コメントは何故7168バイトなのか、の説明です。

 

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}
    //メンバー変数
    char* m_FileName;            //ファイル名(解説:もともとはグローバル変数)
    CFILE m_File;                //ファイルクラス変数(解説:もともとはOnFile()関数のローカル変数)
    CSTR m_Buff;                //エディットコントロール用文字列バッファ(解説:もともとはOnFile()関数のローカル変数)
    int m_LastPage;                //最大ページ数(解説:新設-最終ページを記録する)
    int m_Page;                    //表示ページ数(解説:新設-現在の表示ページを記録する)
//(解説:1回にすべて表示して終わる以前のDumpと異なり、いつもフィル内容を保持しておき、どの部分も選択して表示できるようにファイルデータ、最大ページ数と現在のページ数をメンバー変数として記録しておくようにしました。)
   

 

 //メニュー項目、ダイアログコントロール関連
    bool OnFile();
    bool OnTop();    //(解説:新設-先頭のページへ移動) 
    bool OnBefore();    //(解説:新設-ひとつ前のページへ移動)
    bool OnNext();    //(解説:新設-ひとつ後ののページへ移動)
    bool OnBottom();    //(解説:新設-後尾のページへ移動)
    bool OnIdok();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);
    bool OnDropFiles(WPARAM, LPARAM);
    //ユーザー定義関数
    bool SetPage(int);    //(解説:新設-ページ数をISC_PAGEエディットボックスに表示)
    bool ShowData(int);    //(解説:新設-ファイルデータを16進数表示でIDC_EDITエディットウィンドウに表示)
    bool ChangeTopStatus(bool);    //(解説:新設-先頭に戻るボタンやひとつ前に戻るボタンの状態を変更する)
    bool ChangeBottomStatus(bool);    //(解説:新設-後尾に戻るボタンやひとつ後に戻るボタンの状態を変更する)
    bool GoMiddle(WPARAM);    //(解説:新設-ページエディットボックスをクリックすると中央のページに移動する)
};
//(解説:今回の改良の目玉であるページ移動処理の為に新設したコントロールの処理を追加しています。)
 

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

BEGIN_MODELESSDLGMSG(ModelessProc, Dump)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(Dump, IDC_FILE, OnFile())
    ON_COMMAND(Dump, IDC_TOP, OnTop())    //(解説:新設)
    ON_COMMAND(Dump, IDC_BEFORE, OnBefore())    //(解説:新設)
    ON_COMMAND(Dump, IDC_NEXT, OnNext())    //(解説:新設)
    ON_COMMAND(Dump, IDC_BOTTOM, OnBottom())    //(解説:新設)
    ON_COMMAND(Dump, IDOK, OnIdok())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_SIZE(Dump)
    ON_COMMAND(Dump, IDC_PAGE, GoMiddle(wParam))
    ON_DESTROY(Dump)
    ON_(Dump, WM_DROPFILES, OnDropFiles(wParam, lParam))
END_DLGMSG

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

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

////////////////////////
//CARGインスタンスの作成
////////////////////////
CARG arg;

////////////////
//グローバル変数
////////////////
//システム関連
bool g_ByFile = FALSE;    //ファイルドロップによる起動フラグ    //(解説:これもメンバー変数にしてもよいのですが、外部変数のまま残しました。)

 

Dumpの改良を行っていて、動作には満足が行きましたが、コントロールの説明が欲しいなと、ツールヒントトロールを付けようと思い、汎用関数を作ってヘッダーファイルに入れました。これを使って、ラベル、エディットボックス、ボタンを張り付けたダイアログの動作確認サンプルプログラムを作り、動かしてみたのですが、単にツールヒントコントロールが表示されないのみならず、エディットボックスにフォーカスが当たると反応しなくなり、固まるというトラブルに陥りました。

 

更に「もしや」と思って同様にSkeltonWizardでダイアログベースでつくったプログラム(例:BCCMaker、Renamer)などを確認すると、

(1)BCCMakerはツールバーやステータスバーのツールチップが出ないことから既にモードレスダイアログからモーダルダイアログ化しており正常動作、

(2)Renamerは上記サンプルプログラムと同じく、エディットボックスが文字処理を全く受け付けなくなっていて、同じ症状、

であることを確認できました。これはBCCSkeltonのバグを示唆し、大いに焦りました。しかし...

 

バグ探しはミステリー物の謎ときと同じで、それ自体が一種のエンターテインメントで、プログラミングの醍醐味です。

 

今回も、不具合の現象(症状)を色々と集めてそれから考えられることを一つ一つつぶしてゆくことになります。
 

ヒント1:「SkeltonWIzardが作るモードレスダイアログべースのプログラムがやばい」→cppファイルのCreate関数、Loop関数、hファイルのBEGIN_MODELESSDLGMSGマクロが容疑者?

ヒント2:「エディットボックスでマウスは正常動作するが、文字が入力や削除できない」→エディットボックスのメッセージ(WM_KEYDOWN等)が正しく処理されていない→しかしラベルやボタンは動いているし、BCCMakerのように(サンプルプログラムやRenamerも)DoModal、EndModal関数でモーダルダイアログにすると正常に動作する。

ヒント3:また、不具合プログラム(スレッド)は正常終了しないようで、タスクマネージャーのお世話になります→やはりWM_QUITメッセージなどが処理されていない?

 

以上の内容を頭に入れてCDLG.hを眺めてゆくと、Loop関数のところで違和感を感じます。(青字は本ブログで書いた補足説明です。)

//メッセージループ
UINT CDLG::Loop() {

    while(GetMessage(&m_Msg, m_hWnd 0, 0)) {  //m_hWndはCDLGクラスのダイアログのハンドルです
        if(!TranslateAccelerator(m_hWnd, m_Accel, &m_Msg) &&  //アクセラレーターに対応するようにしています
        !IsDialogMessage(m_hWnd, &m_Msg)) {
            TranslateMessage(&m_Msg);
            DispatchMessage(&m_Msg);
        }
    }
    return m_Msg.wParam;
}

しかし何が違和感の原因なのかが分からず、とりあえず、(CDLGクラスのもととなった)CSDIクラスと比較します。

//メッセージループ
UINT CSDI::Loop() {

    while(GetMessage(&m_Msg, NULL, 0, 0)) {
        //モードレスダイアログがある場合
        if(!IsDialogMessage(m_hDlg, &m_Msg) &&  //SDIウィンドウは一つだけモードレスダイアログが使えますが、m_hDlgはそのハンドルです
        //アクセラレーター処理   //CSDIクラスもアクセラレーターに対応するようにしています
            !TranslateAccelerator(m_hWnd, m_Accel, &m_Msg)) {  //m_hWndはメインのSDIウィンドウのハンドルです
            TranslateMessage(&m_Msg);
            DispatchMessage(&m_Msg);
        }
    }
    return m_Msg.wParam;
}
あ"~

CSDI:GetMessage(&m_Msg, NULL, 0, 0))

CDLG:GetMessage(&m_Msg, m_hWnd 0, 0)

ちがうじゃん!( 因みにCMDIのLoop関数もCSDIと同じようにNULLになっています。)

本来GetMessage関数でプログラムのスレッド内のすべてのメッセージを取り出して処理(NULL)すべきところ、CDLGではダイアログのメッセージしか扱っていない(m_hWnd)ので、ダイアログ自体はIsDlgMessage関数で処理されますが、他の(コントロールを含む)ウィンドウのTranslateMessageDispatchMessageがなされていなかったことが判明しました。これが原因であれば上記の症状も腑に落ちます。

 

ということで、

CDLG:GetMessage(&m_Msg, NULL 0, 0)

と修正し、不具合のあったプログラムを再コンパイルし、正常動作を確認しています。また、修正版を本日Vectorに差し替えアップロードしています。(注)

 

注:実は前回のIDListのプルグラミングで(症状は未だ経験していませんが)CSTRにバグを発見、これも同時に修正(赤字が修正箇所)してアップロードしています。

修正1:        DWORD dwTextLength = lstrlen(m_str) + 1;   //バッファから文字列長取得し、NULL終端も加算

修正2:        m_str[dwFileSize - 1] = 0;                          //念のため、バッファの終端にNULLを置く

 

お粗末様でした。

前回は修正Dump.rc、ResDump.hファイルをやりましたが、今回は主役(何もしませんが)のDump.cppです。

 

結果的に言うと、ダイアログコントロールの説明文を表示する「ツールヒントコントロール」(要すればツールチップ)の追加に関わる赤字部分の一行追加した以外、何も変更はありません。(しかし、実はこのDumpの改良を機会に、見逃していた致命的なバグを見つけることができました。それは別途ブログで書きます。)

 

//////////////////////////////////////////
// Dump.cpp
//Copyright (c) 03/04/2021 by BCCSkelton
//////////////////////////////////////////
#include    "Dump.h"
#include    "ToolTip.h"
#include    "DumpProc.h"

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

    //データファイル付きで起動する場合(ByFileフラグを立てる)
    if(arg.c() > 1) {
        Dump.m_FileName = arg.v(1);
        g_ByFile = TRUE;
    }

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

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

前回の仕様に基づくリソースの追加を行います。

 

追加するのは、

(1)ファイルの先頭へ行くボタン(Bitmap貼り付け)-IDC_TOP

(2)同Bitmap-IDI_TOP

(3)1ページ戻るボタン-IDC_BEFORE

(4)同Bitmap-IDI_BEFORE

(5)ページ表示R/Oエディットボックス-IDC_PAGE

(6)1ページ進むボタン-IDC_NEXT

(7)同Bitmap-IDI_BEFORE

(8)ファイルの末尾へ行くボタン-IDC_BOTTOM

(9)同Bitmap-IDII_BOTTOM

です。

 

張り付けるビットマップは16x16のサイズでEZImageでつくりましょう。私はBCCFormandBCCSkeltonのSampleBMPフォールダーから左右三角形のビットマップを選び、ちょっと色を変えています。

 

私の修正したrcファイルとRes.hファイルは次の通りです。参考にしてください。(なお、#define文が増えているのは、ステータスバーにツールチップを付けるためです。)

 

【Dump.rc】

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------
#include    "ResDump.h"
#define        SBT_TOOLTIPS        0x0800    //スティタスバー用スタイル

//----------------------------------
// ダイアログ (IDD_DUMP)
//----------------------------------
IDD_DUMP DIALOG DISCARDABLE 0, 0, 360, 360
EXSTYLE WS_EX_DLGMODALFRAME | WS_EX_ACCEPTFILES
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | DS_3DLOOK | DS_CENTER
CAPTION "Dump - ファイルダンプユーティリティ"
FONT 8, "MS 明朝"
{
 CONTROL "ファイル", IDC_FILE, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER, 3, 330, 48, 15
 CONTROL 0, IDC_TOP, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 130, 330, 15, 15
 CONTROL 0, IDC_BEFORE, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 150, 330, 15, 15
 CONTROL "", IDC_PAGE, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_NUMBER | ES_CENTER, 170, 330, 15, 15, WS_EX_CLIENTEDGE
 CONTROL 0, IDC_NEXT, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 190, 330, 15, 15
 CONTROL 0, IDC_BOTTOM, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER | BS_BITMAP, 210, 330, 15, 15
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON | BS_CENTER | BS_VCENTER, 303, 330, 48, 15
 CONTROL "", IDC_EDIT, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_READONLY | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL | ES_LEFT, 3, 1, 349, 325, WS_EX_CLIENTEDGE
 CONTROL "", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | SBT_TOOLTIPS | CCS_TOP | CCS_NOMOVEY, 0, 0, 0, 0
}

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

//--------------------------
// イメージ(IDI_BEFORE)
//--------------------------
IDI_BEFORE    BITMAP    DISCARDABLE    "C:\Users\ysama\Programing\Borland C++\Dump\Before.bmp"

//--------------------------
// イメージ(IDI_NEXT)
//--------------------------
IDI_NEXT    BITMAP    DISCARDABLE    "C:\Users\ysama\Programing\Borland C++\Dump\Next.bmp"

//--------------------------
// イメージ(IDI_TOP)
//--------------------------
IDI_TOP    BITMAP    DISCARDABLE    "C:\Users\ysama\Programing\Borland C++\Dump\ToTop.bmp"

//--------------------------
// イメージ(IDI_BOTTOM)
//--------------------------
IDI_BOTTOM    BITMAP    DISCARDABLE    "C:\Users\ysama\Programing\Borland C++\Dump\ToBottom.bmp"
 

【ResDump.h】

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_DUMP
#define    IDC_FILE        100
#define    IDC_TOP            101
#define    IDC_BEFORE        102
#define    IDC_PAGE        103
#define    IDC_NEXT        104
#define    IDC_BOTTOM        105
#define    IDC_EDIT        106
#define    IDC_STATUSBAR    107

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

//---------------------
//  イメージリソース
//---------------------
#define    IDI_ICON        200
#define    IDI_BEFORE        300
#define    IDI_NEXT        400
#define    IDI_TOP            500
#define    IDI_BOTTOM        600

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

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

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

前回のIDListは実際に使っていることもあり、実用ソフトと呼べますが、その後のネタが浮かびません。そんな折に【昔話】などをシリーズで書いてきましたが、「そういえば、当時はダンプリストを取ってマニュアルで16進数を入れていたなぁ。」などと感慨にふけりました。その流れでBCCForandBCCSkeltonに同梱されているサンプルのDumpを立ち上げてみると、(確かにビットマップやアイコンの現物を調べるために使いましたが)余りのいーかげんさ(注)にちょっと赤面。

注:ファイルを読み込むと冒頭からエディットボックスで閲覧できるのですが、元に戻って見直すことができず、ファイルのすべてを表示する為にMessageBoxを使っているのですが、Dumpの子ウィンドウにするとエディットボックスがDisableとなるので、「えぃっ、いいやっ!」とデスクトップを親にしたのですぐに隠れちゃいます。

 

と、いうことでプログラムの仕様に

(1)エディットボックスに「ページ」を表示し、自由に「ページ移動」ができるようにする。

(2)大きなファイルもあるので、移動は小さくと大きくできるようにする。

を加えました。

 

次にこの移動を可能とするインターフェースをどうするか、があります。一応元のレイアウト(表示用エディットボックス、ファイル読み込みと終了のボタン2つ)はそのままにしようと思うので、ボタン間に置く移動インターフェースが必要です。

最初に思いつくのはUpDown今度ロールです。ボタンは横向きにもなりますし。

しかし、大きさからいうとかなり使い勝手が悪そうで、且つ大きく移動させるには別のコントロールが必要になってきます。

トラックバー、という手もありますが、これだと1ページごとの移動が戦災になるのとページ数表示がなくなっちゃいますね。

ということで、レトロなWindows 3.1時代の音楽再生ソフトWinPlayer3のボタンを思い出しました。

このノリでR/Oのエディットボックスを挟んではどうだろう、ということで、

に決定。

次回から実際のプログラミングに移ります。