青天の霹靂(英語でもマンマの"a bolt out of the blue"という表現があります)です。

 

一昨日は中華風の鶏そばを、昨日は久々にスパゲッティボロネーゼを作ったので、撮影し、昨日スマホからPCに移して、今朝(私はイメージファイルの既定ソフトにしている)Albumで料理一覧に追加しようとしました。ところが...

 

突然、なんの前触れもなく、ストンと落ちます!

 

これって、例の「バグは続くよ、何処までも」シリーズの症状とよく似ています。

ということでFileNameCleanerをかけても何にもでない。(当たり前ですよね、今回ファイル名は手入力しましたので。)

因みにDLLのFileListから読み込んだら、なんとまた落ちるじゃないですか?

 

因みにAlbum専用の*.albファイルフォールダーの26ファイルの内、読み込めたのが2つ。(逆に何故読み込めるのか?が気になります。)スタンドアロンのFileListでも試験してみましたが、これは大体打率5割くらいか?

 

しかし、

 

何が原因なのか全然わかりません!

 

FileList、ALBUM共に今年の2~4月の間のexe、dllファイルですし、今月も使っていましたが、その時までは完動していました。(その間に変化があったとすればWindows Updateですか。)

 

丁度ネタが切れていたので、この原因探しをブログでやりましょうか、ね?

 

ちゃんと"DICE"クラスウィンドウ(ダイアログコントロール)であるDiceCtrlが動くかどうか、テスト用に「チンチロリン」というサイコロ賭博の親のプレイをシミュレートするアプリを作ってみます。題して(チンチロリンの米国別名)"Dice"です。

 

1.リソース(Dice.rc)

何度も繰り返して恐縮ですが、BCCForm and BCCSkeltonでは、先ずリソースを確保してからコーディングするので、チンチロリンをどう見せるか、から入ります。(といっても、サイコロ三つを振るのはDiceCtrlを使うのですが。なお、ResDice.hは定数定義だけなので省略します。)

 

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 296, 141
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_CENTER | DS_SETFONT
CAPTION "Dice - ChinChiroRin"
FONT 8, "Times New Roman"
{
 CONTROL "", IDC_D1, "DICE", WS_CHILD | WS_VISIBLE | WS_DLGFRAME, 8, 9, 48, 42, WS_EX_WINDOWEDGE
 CONTROL "", IDC_D2, "DICE", WS_CHILD | WS_VISIBLE | WS_DLGFRAME, 85, 9, 48, 42, WS_EX_WINDOWEDGE
 CONTROL "", IDC_D3, "DICE", WS_CHILD | WS_VISIBLE | WS_DLGFRAME, 162, 9, 48, 42, WS_EX_WINDOWEDGE

 CONTROL "", IDC_EDIT, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | ES_LEFT, 6, 61, 282, 60, WS_EX_CLIENTEDGE
 CONTROL "", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | CCS_TOP | CCS_NOMOVEY, 0, 129, 0, 0
 CONTROL "How to use", IDC_HOWTOUSE, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 231, 3, 51, 15
 CONTROL "Roll", IDC_ROLL, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 231, 22, 51, 15
 CONTROL "Exit", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 231, 42, 51, 15
}
//(解説:ダイアログに表示用のEDITコントロール、ステータスバーと操作用のボタン3つ付けて、後は単純にサイコロ("DICE")を三つ並べただけです。"DICE"コントロールはまだWindows OSには存在していないことにご注意を。なおデフォルトとして表示はTimes New Roman英語にしてあります。)


//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "Dice.ico"
//(解説:ちょいと雰囲気を出すために、サイコロにしてみました。)


//--------------------------
// サウンド(IDS_DICE)
//--------------------------
IDS_DICESOUND    WAVE    "DiceSound.wav"

//(解説:これも雰囲気を出すために、コントロールのデフォルト音源ではなく、丼に3個のサイコロを転がすチンチロリンのフリー音源にしてみました。)

/*
//--------------------
// ストリングテーブル
//--------------------
STRINGTABLE DISCARDABLE
{
 Str_00, "Dice - Chinchirorin"
 Str_01, "How to use"
 Str_02, "Roll"
 Str_03, "Exit"
 Str_04, "Record is given here"
 Str_05, "Dice - チンチロリン"
 Str_06, "使い方"
 Str_07, "投賽"
 Str_08, "終了"
 Str_09, "戦績表示"
}
*/

//(解説:ストリングテーブルはこんな表記をするのですが、プログラムの中でロードする必要があり、扱いが面倒なので「やんぴ」しました。)

 

2.CPPファイル

これは何の変哲もないプログラムです。(2重起動を許可しているのでいくつも日本語、英語で賭場を立てることができますよ。)

SkeltonWizardはModelessダイアログで出力したのですが、私が勝手にモーダルダイアログに変えちゃいました。

ポイントは"DICE"を登録するところです。

 

//////////////////////////////////////////
// Dice.cpp
//Copyright (c) 05/21/2022 by BCCSkelton
//////////////////////////////////////////
#include    "Dice.h"
#include    "DiceProc.h"

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

    //"DICE"コントロールの登録
    InitDiceCtrl(hInstance);

    //(解説:DiceCtrl.cppのこの関数を確認してください。)

 

    //モーダルダイアログを作成DoModal(HWND, LPCTSTR, DLGPROC, HINSTANCE);
    return Dice.DoModal(NULL, "IDD_MAIN", ModalProc, hInstance);
}
 

3.Dice.hファイル

モードレスダイアログ用のファイルをモーダルダイアログ様に書き換えてます。(といってもほとんど同じなのでそのままでも動きますけどね。)

後はDiceCtrl.dllを使うので、そのヘッダーとライブラリーを使うこと、InitDiceCtrl関数をインポートすること、WAVEを使うのでwinmm.libを使うこと、バイリンガル文字列変数を定義することなどでしょうか?

 

//////////////////////////////////////////
// Dice.h
// Copyright (c) 05/21/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResDice.h"
//Dll.hヘッダー
#include    "Dll.h"

//(解説:DLLの場合このヘッダーを使ってください。)

//DiceCtrlのヘッダー
#include    "DiceCtrl.h"
//DiceCtrl.lib をリンクする
#pragma comment(lib, "DiceCtrl.lib")
IMPORT bool InitDiceCtrl(HINSTANCE);

//(解説:DiceCtrlを使えるようにします。旧いファイルには懐かしい「CDICECTRLのヘッダー」と書かれています。)

//winmm.lib をリンクする
#pragma comment(lib, "winmm.lib")

//(解説:チンチロリンの音源を使うので。)

//言語切替用フラグ、フォントとコンスタント文字列
bool g_JAPANESE = FALSE;
CFONT g_Font;
char* Str_00 = "Dice - Chinchirorin";
char* Str_01 = "How to use";
char* Str_02 = "Roll";
char* Str_03 = "Exit";
char* Str_04 = "Record is given here";
char* Str_05 = "Wanna exit?";
char* Str_06 = "Confirmation";
char* Str_07 = "About \"Chinchirorin\"";
char* Str_08 = "You've got %d of dice %d.";
char* Str_09 = "You have %d more time(s) to try.";
char* Str_10 = "Dice - チンチロリン";
char* Str_11 = "使い方";
char* Str_12 = "投賽";
char* Str_13 = "終了";
char* Str_14 = "戦績表示";
char* Str_15 = "終了しますか?";
char* Str_16 = "確認";
char* Str_17 = "チンチロリンについて";
char* Str_18 = "賽の目:%d (第%d番賽)";
char* Str_19 = "後%d回、賽を振ることができます。";

//(解説:バイリンガルメッセージにしたので。)


/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}

    //(解説:このコンストラクターを使っていますが、2重起動可能です。)

    //メンバー変数
    int m_Times = 3;

    //(解説:親は目が出る迄3回サイコロを振ることができるので。)

    //メニュー項目、ダイアログコントロール関連
    bool OnHowtouse();
    bool OnRoll();
    bool OnIdok();
    bool OnDice1(WPARAM);
    bool OnDice2(WPARAM);
    bool OnDice3(WPARAM);

    //(解説:上からゲーム説明ボタン、サイコロを振るボタン、終了ボタンと”3つの"DICE"コントロールからのメッセージ用です。)

    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
};

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

BEGIN_MODALDLGMSG(ModalProc, Dice)    //コールバック関数名
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(Dice, IDC_HOWTOUSE, OnHowtouse())
    ON_COMMAND(Dice, IDC_ROLL, OnRoll())
    ON_COMMAND(Dice, IDOK, OnIdok())
    ON_COMMAND(Dice, IDC_D1, OnDice1(wParam))
    ON_COMMAND(Dice, IDC_D2, OnDice2(wParam))
    ON_COMMAND(Dice, IDC_D3, OnDice3(wParam))

    //(解説:"DICE"コントロールはWin3.x風にWM_COMMANで親に通知し、wParamの上位16ビットにサイコロの目が入っています。)

    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
END_DLGMSG

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

//(解説:ユーザーへのメッセージの補完用です。)

開発ネタシリーズ】で開発仕様やイメージは紹介しているので、いよいよコーディングを紹介します。

 

1.リソース

使用するリソースはコントロール上で右クリックした場合のポップアップメニュー、1~6までのサイコロのビットマップとサイコロを一個台や机の上で転がしたWAVE音源です。ビットマップとWAVファイルはウェブ上でフリー素材を見つけました。

BCCFormはWAVE音源をカバーしていないので、「手書き」で追加します。書き方はイメージファイルと同じですね。

 

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

//--------------
// Popupメニュー
//--------------
IDM_POPUP MENU DISCARDABLE
{
    POPUP "Popup(&P)"
    {
        MENUITEM "Roll (&R)", IDM_ROLL
    }
}

//--------------------------
// イメージ(IDI_D1)
//--------------------------
IDI_D1    BITMAP    DISCARDABLE    "D1.bmp"
//--------------------------
// イメージ(IDI_D2)
//--------------------------
IDI_D2    BITMAP    DISCARDABLE    "D2.bmp"
//--------------------------
// イメージ(IDI_D3)
//--------------------------
IDI_D3    BITMAP    DISCARDABLE    "D3.bmp"
//--------------------------
// イメージ(IDI_D4)
//--------------------------
IDI_D4    BITMAP    DISCARDABLE    "D4.bmp"
//--------------------------
// イメージ(IDI_D5)
//--------------------------
IDI_D5    BITMAP    DISCARDABLE    "D5.bmp"
//--------------------------
// イメージ(IDI_D6)
//--------------------------
IDI_D6    BITMAP    DISCARDABLE    "D6.bmp"
 

//--------------------------
// サウンド(IDS_DICE)
//--------------------------
IDS_DICE    WAVE    "Dice.wav"

 

リソースのヘッダーファイルは以下の通り。WAVE音源は文字列のまま使うので、整数値は与えていません。

 

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  メニューリソース
//---------------------
#define    IDM_ROLL        100

//---------------------
//  イメージリソース
//---------------------
#define    IDI_D1            200
#define    IDI_D2            201
#define    IDI_D3            202
#define    IDI_D4            203
#define    IDI_D5            204
#define    IDI_D6            205
 

2.ヘッダーファイル

先ずDLLを作る際に必要な(前にも使った)DLL.hを使います。

 

/////////////////////////////////////////////////////////////////////////////////////////
// DLL.h for DLL Programs
//https://docs.microsoft.com/ja-jp/cpp/cpp/definitions-and-declarations-cpp?view=msvc-170
/////////////////////////////////////////////////////////////////////////////////////////
#define    EXPORT extern "C" WINAPI __declspec(dllexport)
#define    IMPORT extern "C" WINAPI __declspec(dllimport)
 

DLL固有のヘッダーファイルは次の通り。あっさりしていますね。

 

//////////////////////////////////////////
// DiceCtrl.h
// Copyright (c) 05/17/2022 by BCCSkelton
//////////////////////////////////////////
//Windows用ヘッダー
#include    <windows.h>
//リソースIDのヘッダー
#include    "ResDiceCtrl.h"
//定数定義
#define        DM_ROLL        WM_USER + 1        //Roll実行メッセージ(カスタムコントロールなのでWM_USERを使う)
#define        SIZE        128                //ダイスサイズ既定値
 

先ずBCCSkeltonを使っていないのでWindowsプログラミングに必須のwindows.h(だけ)を使っています。

後は定数定義だけですが、"DICE"コントロール(ウィンドウクラス)固有のメッセージとしてDM_ROLLをWM_USER領域で規定しています。

 

3.DLLMain、DiceProcコールバック、InitDiceCtrl各関数

最後に(これもあっさりしていますが)、エントリーポイントとなるDLLMain関数、このウィンドウクラス(==ダイアログコントロール)共用のコールバック関数(DiceProc)と初期化関数(InitDiceCtrl)を解説します。

 

//////////////////////////////////////////
// DiceCtrl.cpp
//Copyright (c) 05/17/2022 by BCCSkelton
//////////////////////////////////////////
#include    "DiceCtrl.h"
#include    "Dll.h"
//(解説:↑で紹介したヘッダーを読み込みます。)
 

///////////
//外部変数
///////////
HINSTANCE g_hInstance;
HBITMAP g_hDice[6];
int g_Dice = 0;
//(解説:上から子のDLLのインスタンス、サイコロ画像のビットマップハンドル配列、サイコロの目の整数です。)

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

    //インスタンスの取得
    g_hInstance = hInstance;
    return TRUE;
}
//(解説:ここですることは、リソースを読み込む為にDLLのインスタンスを記録することです。)

/////////////////////////
//"DICE"コールバック関数
/////////////////////////
LRESULT CALLBACK DiceProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
//(解説:このコールバック関数は(ウィンドウクラス)"DICE"コントロールが全て飛んでくる(従って引数はそれぞれ異なる)共用のコールバック関数です。それぞれのコントロールで処理は異なっても、処理の枠組みは共通です。)

    //変数宣言
    LONG style;
    LONG estyle;

    //(解説:この変数はウィンドウスタイルを変えるために必要です。)
    RECT rec;
    //(解説:これはコントロールの外寸、内寸を取るために必要です。)
    HMENU hMenu, hPopupMenu;

    //(解説:コントロール状で右マウスクリックでポップアップメニューを出すために必要です。)
    POINT pt;

    //(解説:同上。)
    PAINTSTRUCT ps;
    HDC hDC, hCompDC;
    //(解説:コントロール全体にサイコロ画像を描画する為に必要です。)
    //メッセージ処理
    switch(Msg) {
    case WM_CREATE:
        //乱数の初期化
        srand(time(NULL));
        //(解説:乱数の初期化の定番です。)
        //ダイスの目のイメージを読み込む
        for(int i = 0; i < 6; i++) {
            g_hDice[i] = (HBITMAP)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_D1 + i), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
            if(!g_hDice[i])
                MessageBox(hWnd, "ダイスのビットマップが読めません", "エラー", MB_OK | MB_ICONERROR);
        }
        //(解説:サイコロの目の画像のID整数を並べているのでこのような処理ができます。)
        break;
    case WM_RBUTTONDOWN:
        //ポップアップメニューの読込
        hMenu = LoadMenu(g_hInstance, "IDM_POPUP");
        hPopupMenu = GetSubMenu(hMenu, 0);
        //ウインドウの位置情報を取得
        GetWindowRect(hWnd, &rec);
        //ポップアップメニューの表示場所を設定
        GetCursorPos(&pt);
        //ポップアップメニューの表示
        TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hWnd, &rec);
        //ポップアップメニューの開放
        DestroyMenu(hMenu);
        //(解説:マウス右クリックによるポップアップメニューの定番処理です。)
        break;
    case WM_COMMAND:
        if(LOWORD(wParam) != IDM_ROLL)
            return DefWindowProc(hWnd, Msg, wParam, lParam);
        //(解説:ポップアップメニューの"IDM_ROLL"(それしかないが)が押された場合に下に処理が流れます。)
    case WM_LBUTTONDOWN:
        //(解説:マウス左クリックの場合下に流れますので、ポップアップメニューと合流してサイコロ音を鳴らします。)
        PlaySound("IDS_DICE", g_hInstance, SND_RESOURCE | SND_ASYNC);
    case DM_ROLL:
        //(解説:固有のメッセージDM_ROLLの場合にはサイコロ音はなりませんが、ここから全て処理が共通となります。)
        //ウィンドウを押し下げたようにする
        style = GetWindowLong(hWnd, GWL_STYLE) ^ WS_DLGFRAME;
        SetWindowLong(hWnd, GWL_STYLE, style);
        estyle = GetWindowLong(hWnd, GWL_EXSTYLE) ^ WS_EX_WINDOWEDGE;
         //(解説:ウィンドウ(拡張)スタイルを変更します。XORでビットを消すのも定番ですね。)
        estyle |= WS_EX_CLIENTEDGE;
        //(解説:ウィンドウ拡張スタイルを変更します。赤と水色のスタイルが一番「高低差」を感じさせます。)
        SetWindowLong(hWnd, GWL_EXSTYLE, estyle);
        SetWindowPos(hWnd, NULL, 0, 0, 0, 0, (SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED));
        //(解説:変更したスタイルでフレーム迄再描画します。)
       //乱数でサイコロの目を決める
        for(int i = 0; i < 10; i++) {
            g_Dice = rand() % 6;    //m_Dice = 0 - 5
            SetProp(hWnd, "DICE_NUM", (HANDLE)g_Dice);    //そのウィンドウにサイコロの目を保存する
            InvalidateRect(hWnd, NULL, TRUE);            //領域無効化
            UpdateWindow(hWnd);                            //再描画命令
            Sleep(50);
        //(解説:乱数で0~5のサイコロの目を求め、各"DICE"コントロールに「埋め込み」、再描画して待機します。)
        }
        //ウィンドウを元に戻す
        style |= WS_DLGFRAME;
        SetWindowLong(hWnd, GWL_STYLE, style);
        estyle |= WS_EX_WINDOWEDGE;
        estyle ^= WS_EX_CLIENTEDGE;
        SetWindowLong(hWnd, GWL_EXSTYLE, estyle);
        SetWindowPos(hWnd, NULL, 0, 0, 0, 0, (SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED));
        //(解説:変更したスタイルを元に戻しフレーム迄再描画します。)
        //親へWin3.xのオマージュで通知コードHIWORD(wParam)の代わりに賽の目のメッセージを送る
        SendMessage(GetParent(hWnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hWnd), g_Dice + 1), LPARAM(hWnd));
        //(解説:WM_COMMANDを使った親への通知です。サイコロの目を返す処理一つしかないのでこれにしましたが、コントロールの通知コードが必要な場合には使えないことにご注意ください。)
        //親からのDM_ROLLメッセージの戻り値として賽の目を返す
        return g_Dice + 1;
        //(解説:親から"SendMessage(hDiceCtrl, DM_ROLL, 0, 0);"で呼ばれた時の戻り値です。)
    case WM_PAINT:
        hDC = BeginPaint(hWnd, &ps);
        //ダイスの描画
            //クライアントエリアサイズの取得
            
RECT rec;
            GetClientRect(hWnd, &rec);
            //ウィンドウプロパティからサイコロの目を取り出す
            g_Dice = (int)GetProp(hWnd, "DICE_NUM");
            //ビットマップの描画
            BITMAP bmp;
            GetObject(g_hDice[g_Dice], sizeof(BITMAP), &bmp);
            hCompDC = CreateCompatibleDC(hDC);
            SelectObject(hCompDC, g_hDice[g_Dice]);
            StretchBlt(hDC, 0, 0, rec.right - rec.left, rec.bottom - rec.top, hCompDC, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
            DeleteDC(hCompDC);

        //(解説:ここがコントロールのクライアントエリア一杯にサイコロの目の画像を描画する処理です。switch内の変数宣言はできないはずですが、赤のコードがエラーが出ずに残ってしまいましたので、本日修正しました。修正版のアップは次の機会に(汗;)しかし、全く同じ変数名がswitch文の中と外で共存できる、というのがC++的ですね。ん、BITMAP変数も宣言できている?はて、面妖な。まっ、いいか。)
        EndPaint(hWnd, &ps);
        break;
    case WM_CLOSE:
        //ダイスの目のイメージを破棄する
        for(int i = 0; i < 6; i++)
            DeleteObject((HGDIOBJ)g_hDice[i]);
        //ウィンドウプロパティを削除する
        RemoveProp(hWnd, "DICE_NUM");

        //(解説:お忘れなく。メモリーリークの原因となります。)

        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    return 0L;
}

EXPORT bool InitDiceCtrl(HINSTANCE hInstance) {

//(解説:やっとここでEXPORTが出てきました。そうです、DLL外部で使う関数はこれ一つなんです。コモンコントロールのInitCommanControl()関数も似たようなことをやっているのかしら?)


    //"DICE"ウィンドウクラスの登録
    WNDCLASSEX WndClass;
    //ウィンドウクラスの登録
    WndClass.cbSize        = sizeof(WNDCLASSEX);            //この構造体のサイズ
    WndClass.style        = CS_HREDRAW | CS_VREDRAW;        //縦横移動、サイズ変更時再描画
    WndClass.lpfnWndProc= DiceProc;                        //コールバック関数ポインター
    WndClass.cbClsExtra    = 0;                            //この構造体以降に置くバイト数
    WndClass.cbWndExtra    = 0;                            //インスタンス以降に置くバイト数
    WndClass.hInstance    = hInstance;                    //このウィンドウのインスタンス
    WndClass.hIcon        = NULL;                            //このウィンドウクラスのアイコン
    WndClass.hCursor    = LoadCursor(NULL, IDC_ARROW);    //このウィンドウクラスのカーソル
    WndClass.hbrBackground = (HBRUSH)WHITE_BRUSH;        //このウィンドウクラスの背景色
    WndClass.lpszMenuName    = NULL;                        //このウィンドウクラスのメニュー
    WndClass.lpszClassName    = "DICE";                    //このウィンドウクラスの名称
    WndClass.hIconSm    = NULL;                            //最小化時の小さいアイコン
    if(!RegisterClassEx(&WndClass)) {
        MessageBox(NULL, "ウィンドウを登録できませんでした。", "エラー", MB_OK | MB_ICONEXCLAMATION);
        return FALSE;
    }
    return TRUE;
}

//(解説:このウィンドウ登録処理のみが外部へ解放されています。また、"DICE"クラスウィンドウが作成される前に登録する必要があることは自明ですね。CPICBOXクラスでは位置が固定できるstaticのダミー関数を作り、その後そのダミー関数からインスタンス固有の子―^るバック関数を呼ぶようにしましたが、DLLベースのウィンドウクラスではコールバック関数を共用とせざるを得ないので、固有の処理はウィンドウ毎のIDで区別する必要があります。)

 

4.結語

以上でDLLを使った「サイコロコントロール」ができました。子の作成過程を見ると、例えばボタンコントロール("BUTTON")はWM_LBUTTONDOWNの際に「浮き上がったボタンの画像」を「押し下げたボタンの画像」に交換し、画像の中に文字を書いて(またはイメージを描画して)、WM_LBUTTONUPの際にそれを元に戻す、という処理をしているのだな、ということがよく分かります。別の言葉で言えば、DLLを使うことにより(今回は「押し下げられたように見せ→サイコロ画像を10回変更し→押し上げられた画像に戻して、最後の画像番号+1を返す」という簡単な処理でしたが、同様に)「ウィンドウクラスを登録する」+「ウィンドウ毎に区別して処理を行う共用コールバック関数を作る」+「処理の結果を呼び出し元や親へ送信する」という機能を備えれば、どのような自作コントロールもできるということです。

 

貴方も貴方だけのダイアログコントロールを作ってみませんか?

今朝も朝の3時に突然「あっ」が降りてきました。いやはや、お恥ずかしい。

 

「できました」と書きましたが、WM_LBUTTONDOWNとIDM_ROLLポップアップメニューについてコントロール(子ウィンドウ)にとって一番大事な「親への通知」を忘れていました。(私と同じで親不孝ですね。)

 

さて、では親へ通知しようということでコードを追加しようとして不図考えました。

 

【Win3.x (16 bit) 時代】

"WM_COMMAND"が使われ、(当初はWPARAM(WORDが16ビット)にコントロールコードが入り、LPARAM(LONGが32ビット)にコントロールIDとウィンドウハンドルが入っていたそうですが、後の32 bit時代になり)WPARAM(32ビット)にコントロールID(LOWORD)とコントロールコード(HIWORD)が入り、LPARAM(32ビット)にウィンドウハンドルが入ります。

 

【Win32時代】

"WM_NOTIFY"とNMHDR構造体(注1)が使われ、(特にコモンコントロールなど)複雑な動作をするコントロールになると金魚の糞(注2)を付けて処理することになりました。要すればNMHDR構造体にWin3.x時代の情報(HWND、CtrlID、Msg)をいれ、その他の情報は「金魚の糞」にして渡す、という方法です。以下の様にして使います。

"    case WM_NOTIFY:
         if(idControl == wParam) {   // コントロールID
             *NMHDR lpnmh = (*NMHDR)lParam;   // NMHDR構造体のアドレス

         }"

更にコモンコントロールでも下位互換を考えてWM_COMMANDと併用しているものもあるようです。(注3)

 

注1:以下がNMHDR構造体ですが、

  typedef struct tagNMHDR {
      HWND hwndFrom;   // コントロールのハンドル
      UINT idFrom;     // コントロールID
      UINT code;       // 通知コード
  } NMHDR;
この"NMHDR"の意味ってwebを漁っても出てこないですね。恐らく"Notification Message of HanDleR"ではないかと思っています。他の「金魚の糞コントロール」ではNMLISTVIEW構造体のようにNotification Message of ListViewのようにコントロール名を付けるものもあれば、日付コントロールのNMSELCHANGE構造体のように動作(Notification Message of Selection Change)を表すものや、ツールバーのTOOLTIPTEXT構造体のように読んで字のごとしでNMが無いものもあり、名づけ規則が一貫していませんね。

注2:以下はリストビューの例

typedef struct tagNMLISTVIEW {
  NMHDR  hdr;  //ここを展開すると↑と同じ
  int    iItem;    //ここ以下が金魚の糞
  int    iSubItem;
  UINT   uNewState;
  UINT   uOldState;
  UINT   uChanged;
  POINT  ptAction;
  LPARAM lParam;
} NMLISTVIEW, *LPNMLISTVIEW;

注3:そういえば両方使えるものがあるな、とおもってググったら、同様の疑問を持った方がいたので助かりました。また、命名規則についての印象も同じですね。(「追記」で「負の遺産」と書かれているのは、ポジティブには「下位互換性」といえるでしょう。)

 

今回の"DICE"コントロールにおける親へ「サイコロを振らせられて、この目をだしたよ」という通知を行うイベントは、WM_COMMAND(ポップアップメニューのIDM_ROLL)とWM_LBUTTONDOWN<この二つユーザー操作により、共にコントロールの「サイコロの一個の音」を出す>、およびメインウィンドウからSendMessageで処理する「WM_USER + 1(注4)で定義したDM_ROLLメッセージ」の3つあり、親に渡したい情報はいずれも「サイコロの目(このDLLの内部ではゼロベースの int g_Dice + 1)」です。

 

注4:未定義のウィンドウメッセージに独自の定義を与えて利用するにはWM_USERとWM_APPの二つあり、その違いをMicrosoftはこう定義しています。(タグ"WM_USER"とタグ"WM_APP"の内容は同じですね。)

この問題は結構誤解している人が多いので気を付けてください。簡単に言うと、

0~WM_USER(0x8000) - 1   → OSで使います。

WM_USER~WM_APP(0xC000)→ ユーザーコントロール(「たとえばBUTTON、EDIT、LISTBOX、COMBOBOXなどの」「アプリケーションによって定義され(た)プライベートウィンドウクラス内でメッセージを送信するために使用できます。(Microsoft Doc)従って既存のコントロールの改造(カスタムコントロール)の場合に使ってはいけませんが、新規のコントロール(ユーザーコントロール)の場合にはここで独自のメッセージを定義しなければなりません。

WM_APP(0xC000)~0xFFFF →これは例えば特別な処理のためにメインウィンドウのメッセージを作ったり、既存のコントロールをサブクラス化して新しい処理を新しいメッセージで加えたりするような場合(そのアプリケーション限りのメッセージ)に使います。「アプリケーションがプライベートメッセージとして使用するには、3 番目の範囲 (0x8000 から 0xBFFF) のメッセージ番号を使用できます。この範囲内のメッセージは、システム メッセージと競合しません。」(Microsoft Doc) 

 

ということで、コードの比較をしてみましょう。

 

【WM_NOTIFY-Win32】

先ず出来合いのNMHDRではサイコロの目の情報を入れるメンバーが無いので、新しい構造体を定義して外部変数を宣言しましょう。

 

<NMDICE構造体を定義する>

typedef struct tagNMDICE {
    NMHDR hdr;       // コントロールのハンドル
    int dice_num;     // サイコロの目
} NMDICE, *LPNMDICE;
 

<NMDICE構造体の外部変数を宣言する>

NMDICE g_NMdice;

 

<コールバック変数のSwitch(msg)処理>

   case WM_COMMAND:
        if(LOWORD(wParam) != IDM_ROLL)
            return DefWindowProc(hWnd, Msg, wParam, lParam);
    case WM_LBUTTONDOWN:
        PlaySound("IDS_DICE", g_hInstance, SND_RESOURCE | SND_ASYNC);
    case DM_ROLL:
        .
        .
        .
        //親へWin32版メッセージを送る
        g_NMdice.hdr.hwndFrom = hWnd;

        g_NMdice.hdr.idFrom = GetDlgCtrlID(hWnd);

        g_NMdice.hdr.code = 通知コード;

        g_NMdice.dice_num = g_Dice + 1;

        SendMessage(GetParent(hWnd), WM_NOTIFY,  (WPARAM)GetDlgCtrlID(hWnd), (LPARAM)&g_NMdice);
        //親からのDM_ROLLメッセージの戻り値として賽の目を返す
        return g_Dice + 1;    //g_DiceはDLLの外部変数でサイコロの目 - 1(ビットマップハンドル配列に対応)である

 

WM_NOTIFYのWPARAMは、コモンコントロールなどではイベントが発生したコントロールのコントロール IDが指定されますが、ユニークなものであることが保証されていないそうで、コントロールIDはNMHDR(上記の場合hdrメンバー)情報からとるべきだそうです。また通知コードも対象が3つもある共用処理である為、ここにサイコロの目を入れてもよさそうです。となると、NMDICE構造体もいらなくなり、出来合いのNMHDRで行けそうです。以下に上記のコードを書き換えた改造版を載せます。

 

<NMHDR構造体の外部変数を宣言する>

NMHDR g_Hdr;

 

<コールバック変数のSwitch(msg)処理>

   case WM_COMMAND:
        if(LOWORD(wParam) != IDM_ROLL)
            return DefWindowProc(hWnd, Msg, wParam, lParam);
    case WM_LBUTTONDOWN:
        PlaySound("IDS_DICE", g_hInstance, SND_RESOURCE | SND_ASYNC);
    case DM_ROLL:
        .
        .
        .
        //親へWin32版メッセージを送る
        g_Hdr.hwndFrom =hWnd;

        g_Hdr.idFrom = GetDlgCtrlID(hWnd);

        g_Hdr.code = g_Dice + 1;

        SendMessage(GetParent(hWnd), WM_NOTIFY,  (WPARAM)GetDlgCtrlID(hWnd), (LPARAM)&g_Hdr);
        //親からのDM_ROLLメッセージの戻り値として賽の目を返す
        return g_Dice + 1;    //g_DiceはDLLの外部変数でサイコロの目 - 1(ビットマップハンドル配列に対応)である

 

【WM_COMMAND-Win3.x】

何をいまさら16 bit時代の方法で、という感もありますが、Microsoftも下位互換性を確保しているのでおかしいというほどのことはないです。メリットは処理が簡素なコントロールではかんたんであることです。

 

<コールバック変数のSwitch(msg)処理>

    case WM_COMMAND:
        if(LOWORD(wParam) != IDM_ROLL)
            return DefWindowProc(hWnd, Msg, wParam, lParam);
    case WM_LBUTTONDOWN:
        PlaySound("IDS_DICE", g_hInstance, SND_RESOURCE | SND_ASYNC);
    case DM_ROLL:
        .
        .
        .
        //親へWin3.x版メッセージを送る
        SendMessage(GetParent(hWnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hWnd), 通知コード), (LPARAM)hWnd);
        //親からのDM_ROLLメッセージの戻り値として賽の目を返す
        return g_Dice + 1;    //g_DiceはDLLの外部変数でサイコロの目 - 1(ビットマップハンドル配列に対応)である

 

しかし、個々でも同様に返したいサイコロの目は親からのSendMessage("DICE"コントロールハンドル, DM_ROLL, 0, 0,);にしか対応していない為、また通知コードも対象が3つもある共用処理である為、ここは(毒や害にならないので)「サイコロの目(g_Dice + 1)」を通知コードに替えて返しちゃいましょう。以下は変更部分のみ記述します。

 

        //親へWin3.x版メッセージを送る
        SendMessage(GetParent(hWnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hWnd), g_Dice + 1), (LPARAM)hWnd);

 

で、どちらにするかといえば...

 

簡単な方に決まり!

 

ということでWM_COMMANDで渡します。(「いやだ、私はWM_NOTIFYで行く」という人がいれば、↑のコードを参考にして真正版<サイズが大きくなり、コード量も増えます>か簡易版<NMHDRのみで行きます>か選択して変更をお願いします。)

 

最初はあまり気乗りがせず、始めたら思うように行かず熱くなり、最終的には何とか思ったような動作をするようになりましたので、昨日"DICE"コントロールを使ったアプリ、Diceをサンプルに加えて新しい版をアップロードしました。一週間か10日でDLできるようになるでしょう。

 

最終的なコントロールの改善点は、コントロールをマウスでクリックしたり、右クリックで出るポップアップメニューで賽を振るとコントロールレベルで(台や机の上で)転がる音がするようにしたことです。一方、ユーザー定義メッセージ(WM_USER + 1をDM_ROLLとしました)では無音にして、アプリ側の音源(Diceでは丼ぶりに3個のサイコロが降られるチンチロリンの音)を使えます

 

アプリのDiceは「チンチロリン」をシミュレートしようとしましたが、現在は「親として立ち、子方と勝負して負け、親落ちするまで」にしており、子のサイコロプレイや得点等は導入していません。が、やる気になれば、

(1)プレーヤーの設定(乱数で2~8名程度かな?)と持ち点付与(これも乱数で変化を持たせた方がよいでしょう)

(2)親の選出(これも乱数)

(3)子の賭け金設定(これはユーザーが関与すべきか、何らかの自動化にするか迷うところです)

(4)親の勝負(現在実現しているパート)

(5)子の勝負

(6)勝敗と得点

(7)(親落ちを含めた)次巡初期化

で行けると思います。

しかし、今回は「サイコロコントロールをDLLでつくってみる」というのが命題であり、チンチロリンはそのサンプルソフトなので、そこまで深追いしません。ここらで手じまいして皆さんにご紹介し、その後は皆さんで発展させることができるようにしてあります。

 

123が出たので親落ちします。

 

今日はチンチロリンの「目」や「役」の判断条件を考えて、論理条件にまとめました。恐らくこれでよいでしょう。(まぁ、間違っていたら後で直せばよいんだし。)

 

そんなこんなで、検討する際にメモッた開発用表示も日英両語にしたので(↓)、もったいないから残しておきます。

/* 開発時のテスト用
    if(g_JAPANESE) 
        str = 
        "/////////////////////////////////////////////////////////////////\r\n"\
        "// チンチロリンの目、役の判定条件について\r\n"\
        "// 1 → 0 0 0 1\r\n"\
        "// 2 → 0 0 1 0\r\n"\
        "// 3 → 0 0 1 1\r\n"\
        "// 4 → 0 1 0 0\r\n"\
        "// 5 → 0 1 0 1\r\n"\
        "// 6 → 0 1 1 0\r\n"\
        "//「アラシ」\r\n"\
        "// num[0] | num[1] | num[2](== num[0] & num[1] & num[2])== num[0];\r\n"\
        "// 全ての要素のOR(またはAND)がいずれも要素である場合。\r\n"\
        "//「456」\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 7 && num[0] & num[1] & num[2] == 4\r\n"\
        "// 各桁のビットが奇数個立っており、全ての要素が4以上である場合。\r\n"\
        "//「123」\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 0 && num[0] | num[1] | num[2] == 3\r\n"\
        "// 各桁のビットが偶数個立っており、全ての要素が3以下である場合。\r\n"\
        "//「目」\r\n"\
        "// num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]\r\n"\
        "// 二つの要素のいずれかが等しい場合。\r\n"\
        "// なお、3つの要素の排他的論理和が最後の目となる。\r\n"\
        "/////////////////////////////////////////////////////////////////\r\n";
    else
        str = 
        "/////////////////////////////////////////////////////////////////\r\n"\
        "// Aoubt the conditions to determine hands of \"Chinchirorin\"\r\n"\
        "// 1 → 0 0 0 1\r\n"\
        "// 2 → 0 0 1 0\r\n"\
        "// 3 → 0 0 1 1\r\n"\
        "// 4 → 0 1 0 0\r\n"\
        "// 5 → 0 1 0 1\r\n"\
        "// 6 → 0 1 1 0\r\n"\
        "// <Storm>\r\n"\
        "// num[0] | num[1] | num[2](== num[0] & num[1] & num[2])== num[0];\r\n"\
        "// In case that all numbers' OR (or AND) is any of the numbers\r\n"\
        "// <456>\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 7 && num[0] & num[1] & num[2] == 4\r\n"\
        "// In case that every digit has odd of \'1\' AND all numbers are more than 3.\r\n"\
        "// <123>\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 0 && num[0] | num[1] | num[2] == 3\r\n"\
        "// Incase that every digit has even of \'1\' AND all numbers are less than 4.\r\n"\
        "// <Point>\r\n"\
        "// num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]\r\n"\
        "// In case that any of two numbers are same.\r\n"\
        "// In such case, the other number must be XOR of the three numbers.\r\n"\
        "/////////////////////////////////////////////////////////////////\r\n";

*/
    int Num_OR = num[0] | num[1] | num[2];
    int Num_AND = num[0] & num[1] & num[2];
    int Num_XOR = num[0] ^ num[1] ^ num[2];
    int Num_EQ = (num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]);
/* 開発時のテスト用
    wsprintf(buff, "num[0]= %d, num[1] = %d, num[2] = %d\r\n", num[0], num[1], num[2]);
    str = str + buff;
    wsprintf(buff, "num[0] | num[1] | num[2] = %d\r\n", Num_OR);
    str = str + buff;
    wsprintf(buff, "num[0] & num[1] & num[2] = %d\r\n", Num_AND);
    str = str + buff;
    wsprintf(buff, "num[0] ^ num[1] ^ num[2] = %d\r\n", Num_XOR);
    str = str + buff;
    wsprintf(buff, "(num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]) = %d\r\n", Num_EQ);
    str = str + buff;
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)str.ToChar());

*/

 

で、「wiki『チンチロリン』の英語版なんてないよね?」と調べたら、なーんとあるのよね。さらに、

なーんと、なーんと、Cee-Loという名前でアメリカはハーレムやブロンクス等のディープなブラックコミュニティでHipHopの方々に愛されていたというじゃありませんか?

 

マジ? Really? チンチャ?

 

 

HipHopとかRapがお好きな方はこちらもどうぞ。

 

 

 

またまた早朝3時にヒラメキが降りてきました。

 

「ウィンドウ毎に固有の値があるなら、ウィンドウにそれを持たせりゃいーだけじゃん?」

 

ということで、(既にBCCSkeltonのCPICBOXユーザーコントロールでは使っていたのですが...汗;)SetProp、GetProp、RemoveProp関数を使って"DICE"コントロールが共用するコールバック関数でウィンドウ毎の値を処理することができるようになりました。(但し、それでも描画は最後に纏めて3つの賽が描かれますが、これはいかんともしようがなさそうです。)

 

では「纏めていこう」と思ったのですが、今まで英語で書いてきてチンチロリンの概要とルールを英語で書いていたら「結構、得意でない人にはきついかも?」と感じ始め、「では、日英両語で行こう!」と方針転換。

 

バイリンガル新方針に沿ってどうするかを検討しましたが、

(1)BCCFormがあるので、「この日の為のSTRINGTABLEじゃないの?」と思いましたが、こいつはLoadString関数などを使うので結構めんどくて、「やっぱやめっ!」ということにしました。

(2)日英両語というと、矢張り#ifdef~#else~#endifのコンパイラースイッチを想定しちゃいますが、これだと日英切替の度にコンパイルが必要ですし、そうなれば「日本語オンリーさん」「英語オンリーさん」が出てきちゃうじゃないですか?なので、「日英両語動的選択」にしようと考えました。

(3)そうなると、日英両語切替は (i) 文字列の他、(ii) ウィンドウやダイアログコントロールのフォント、(iii) 言語選択の方法とすべての文字表示の設定を行うことが必要です。それぞれ(i) 日英両文字列のポインターを用意、(ii) BCCSkeltonのCFONTクラスを使うと便利なのでそれを使い、影響のあるコントロールにはWM_SETFONTを送り、(iii) WM_INITDIALOGで選択ダイアログを出すようにしました。

 

結果、こうなりました。

 

 

いよいよ後は「ルールに基づく賽の目評価」と戦績ログをどうするか、ですね。

 

【無駄話の昔話です。昭和レトロが好きな方はお読みください。】

ps. さてサイコロのコントロールは前から興味がありましたので、今回一応それらしいものができたので「まぁ、良し」としましょう。また、そのアプリとして「チンチロリン」を選んだのには、実は訳があります。

 

私が高校3年生の夏休み、止せば良いのに麻雀を覚えてしまいました。それからは寝ても覚めてお麻雀、麻雀で、一応何とか現役合格はしましたが、大学へも行かず、雀荘(今の人は分からないよね。麻雀をさせる店舗です)に入りびたりの毎日でした。

 

そんな時に貪るように読み耽っていたのが「阿佐田哲也-「朝だ、徹夜」のごろ合わせだそうです-著『麻雀放浪記』」でした。この四部作は麻雀の闘牌の絶妙さや戦後の日本の情景を見事に表しているのみならず、登場人物のキャラ設定や人間関係、行動傾向等実に「心理学、精神分析学専攻の社会学部の学生」として興味を持ったところとなり、挙句1年留年してしまいました。まぁ、そんなことはどうでもよいのですが。

 

この麻雀放浪記(一)青春篇の冒頭エピソードが「チンチロ部落」でして、この時初めて「チンチロリン」なるサイコロ博打があることを知ったわけです。作中のルールはほぼWikiの説明と符合しますが、5が三つ並ぶ「五ゾロ」(倍付)はWikiには出ていませんね。また、シゴロ(456)は「シンゴロウ」などという渾名も付いていたことが分かりますね。

 

何れにしても、この小説のインパクトから自分でも仲間内で遊んだことがありますし、「民主主義時代のサイコロ博打」として面白いと思います。お粗末様でした。

 

最初はあまり気乗りしなかったのですが、一応手を付けたので作ってみようと始めたら、迷路にはまり、熱くなってしまいました。

 

1.最初CDICEというクラスを作り、CPICBOXの様にダミーのコールバック関数でウィンドウクラス登録をしてウィンドウプロパティに書き込んでCDICEクラスの真正コールバック関数を呼ぶ形や、サブクラス化して乗っ取ってしまう形を考えたのですが、どうもうまくゆきません。

2.又、クラスごとDLLに入れてしまうつもりだったので、クラスのExport、Importもトライアンドエラーで学習しながらの開発で動作試験の結果が思わしくありません。

3.そんな時にまた早朝「あっ、DLLで提供するのはリソースとウィンドウクラスの登録と共通の真正コールバック関数でいいんだ。クラスなんていらないじゃん。」というひらめきが降りてきて、早速BCCSkeltonを捨てて、SDKでDLLを組みます。

4.特にメインプログラムのインススタンスとDLLのインスタンス、いつウィンドウ(今回はダイアログですが)が生成されるかのタイミングが結構重要でした。

 

そうしてやっとこさ、「ウィンドウ版チンチロリン」のプロトタイプが出来上がりました。

(初期状態)

 

が、

DLLのコールバック関数を共用している関係上、3つのサイコロの目を決定するタイミングと(InvalidateRect、UpdateWindowの両関数でWM_PAINTを読んで行う)コントロールの描画のタイミングからい「一挙に行うと最後のサイコロの目で3つとも描画されてしまう」問題が生じました。

 

これを抜本的に解決するには、(非効率ですが)コントロールごとにコールバック関数を独立させるか、オールバック関数を利用しているウィンドウ毎にサイコロビットマップ番号を独立して持たせ、それをDB化して呼ばれる度にウィンドウハンドルからそのウィンドウのサイコロビットマップ番号を検索、抽出するか、或いは(無理やり)描画が完了するまでにウェイトをかけるか、になります。

 

で、

どうしたかというと(一番手を抜いた)最後のウェイト方式をメッセージボックスを使って行うことにしました。(だって、面倒くさいんだもん!)

これで(どんくさいのですが)意図した動作をするようになりましたので、手役を判断させ、ログを採らせて手じまいにしようかと思います。(一応サイコロ単体はボタンの様に見えるようになりましたし、10回ほどクルクル目を変えてゆくことにしました。また、久々に(別に必要性はないのですが、お勉強までに)メッセージ等に英語を使ってしまいましたよ。)

 

2年目に突入しましたが、次に何をやろうか、というネタがあまり思いつきません。

 

何か斬新なダイアログコントロールができたなんてことは無いか、と調べましたが20年前とほぼ同じですね。

前にダイアログアプリのDLL化(FileList、Resizer)とBCCSkeltonでのコントロールの作成(PaintBox)をやりましたので、「DLLで使うダイアログコントロールの開発なんてどうだろう?」と手を付けました。

 

現在進行形ですが、一般論と共にサンプルとして「クリックするとサイコロが回って目が出る」コントロールなどゲームで使えるかな、などと思って始めたのは良いのですが、なかなか思うようにいきません。現在フリーのサイコロ画像を探してきてそれをEZImageで整え(未だ若干数ビット位置の差異があるのですが...)、タイトルバーやシステムボタンもないCSDIでサイコロ画面が変わって目を出すところ迄は来たのですが、次の邪念が生じて意欲がわかなくなりました。

 

(1)クラスを使ってコントロールを記述するのですが、リソースを使うコントロールならSkeltonWizardを使って開発する意味があるだろうと思いましたが、マクロテーブルを含む多くのコードを書き換えてしまうので、余りBCCSkeltonの恩恵が大きくない。また、単体での開発からコントロールに移行するにしても最後はやはり「(親ウィンドウによる)テストプログラム」が必要なら、最初からコントロールとしてWin32SDKで開発した方が早いんじゃない、とかの邪念が出る。

 

(2)ベースのクラスをCSDIにして開発し、(単体での動作試験で)機能が実装されたらWS_CHILDを入れてコントロールにする予定ですが、単体の機能が大したことないと何かやる気が削がれる。(例:サイコロのボタンを押し下げて、サイコロの画像が二転三転し、最後に目が出てボタンが戻るコントロールにしようと思い、既存のウィンドウスタイル、拡張スタイルを使ってみたのですが、余りボタンのような感じが出ませんね。これならボタンコントロールを改造するアプローチの方がよっぽどましかもしれません、という邪念が出る。)

 

ということで、まだご紹介できるようなところまで来ていないのですが、ブログネタとして共有しておきます。(しっかし、サイコロのコントロールを使って何をするのかな?最初はチンチロリンがしたいな、と思いましたが、派手にサイコロが転がるグラフィックは絵心もプログラミング技量もない私には無理筋なので「地味なサイコロ」にとどまるだろうと思うと、矢張りやる気が出てこないんですよねぇ。)

本日で本ブログ開設一周年までこぎつけました。結構息切れです。

 

20年前にシンガポールでつくり始めた(当時の)Borland C++ Compiler(BCC)5.5.1の開発環境を帰国後16年放っておいたのですが、65歳で完全定年になり、ボケ防止にまた触り始めたのがきっかけです。(最初の一年は、事故で失った過去のファイルをまた一から再作成してゆくのに費やしています。それが過ぎてやっと2021年版としてリリースできるようにし、昨年ブログも開設しました。)

 

ブログ開設後、

1.(現在の)Embarcadero BCC102(Embarcadero CLANG C++ Compiler 7.30ベースのEmbarcadero C++ Compiler 7.30 for Win32 )への対応(「BCCSkeltonのClang対応」と「BCCコンパイラーの謎+BatchGood」シリーズ参照)、

2.既に作っていたShell(FileHandlerのCSHELLクラス)、TextToSpeech(TextToSpeechのCVOICEクラス)の他、DirectShow(DirectShowのCDSHOWクラス)やGDI+(AlbumのCALBUMクラス)等(従来のBCC5.5では対応が難しかった)COM等新しめのプログラムにも挑戦し、

3.家訓の「Unicodeには触るな!」をも破ってBCCSkeltonWplus等を追加してFileNameCleanerやWCEditor迄手を出しました。

 

その結果、現在のBCCForm and BCCSkeltonのサンプル数は35迄膨れ上がっています。

 

結構おなかいっぱいです。(というか、家政領域におけるウィンドウプログラミングネタが尽きかけている、といった方が正しいでしょう。)

 

余り焦らずに、次のテーマを考えてみます。(やっぱ、ツールかな?)