前回、大分吹かしましたが、結局ウィンドウプログラムは石盤のビットマップを作らなければならないし、数を多くすると非常に疲れそうなので退けて(尻込みして)しまいました。(また、長い時間をかけて作っても、1、2回見たらすぐに飽きると思うので、むなしい努力かなぁ、とテンションが下がったのも事実です。)

 

と、いうことで、今回は先般のC#プログラミング学習の復習を兼ねてC#のコンソールプログラムにしてみました。

が、

結構当初の目的は十分に満たしていると思います。存分に改造してもっと面白くしてもらって結構ですので、皆さんも「ハノイの塔プログラミング」をお楽しみください。(注)また、このプログラムのウィンドウ盤は文字列を使わないのでBCCFormに向いています。盤や塔や背景の作成をいとわれない方は、↓を参考にBCCFormで作ってみるのも一興でしょう。

注:エディターとBCCForm and BCCSkeltonパッケージのサンプル、MSCompass.exeがあればWin10、11のPCで遊べます。

 

【C#ハノイの塔<コンソール版>】

//////////////////////////////////////////////////////////////////
// TowerOfHanoi.cs
// Tower of Hanoi, a mathimatic game
// https://www.programmingalgorithms.com/algorithm/tower-of-hanoi/
// 使用上の注意:最大の盤数はPoleクラスで10に設定されています。
//                これを変更する場合には、ShowHanoi()メソッドの60を
//                最大盤数 x 6に、MakeDisk()メソッドの10を最大盤数
//                に変更してください。
//                また、ShowHanoi()メソッドで 0.1秒のウェィトを置い
//                ています(Thread.Sleep(100))が変更可能です。
//////////////////////////////////////////////////////////////////

using System;
using System.Threading;

class TowerOfHanoi
{
    //「TowerOfHanoi」クラス共通変数の宣言
    public static readonly string[] disk = {"|", "□", "□□", "□□□", "□□□□", "□□□□□", "□□□□□□", "□□□□□□□", "□□□□□□□□", "□□□□□□□□□", "□□□□□□□□□□"};
    public static Pole[] pole = new Pole[3];
    public static int tire = 0;

    //Main関数
    public static void Main()
    {
        //段数のユーザー設定
        Console.Write("円盤柱の段数(整数)を入力してください:");
        while(!int.TryParse(Console.ReadLine(), out tire) || tire <= 0)
        {
            Console.Write("\n円盤柱の段数(整数)を入力してください:");
        }
        //3本の柱のインスタンス作成
        for(int i = 0; i < 3; i++)
            pole[i] = new Pole();
        //最初の柱に円盤を設置する
        for(int i = 0; i < tire; i++)
            pole[0].push(tire - i);
        //「ハノイの塔」の初期状態を表示
        ShowHanoi();
        Console.WriteLine("開始します。何かキーを入押してください:");
        Console.ReadKey();
        //「ハノイの塔」の開始
        TowerofHanoi(tire, 0, 1, 2);
        //コンソールを閉じさせない為
        Console.WriteLine("終了しました。何かキーを入押してください:");
        Console.ReadKey();
    }

    public static void TowerofHanoi(int diskCount, int fromPole, int toPole, int viaPole)
    {
        if(diskCount > 0)
        {
            TowerofHanoi(diskCount - 1, fromPole, viaPole, toPole);
            pole[toPole].push(pole[fromPole].pop());
            ShowHanoi();
            Thread.Sleep(100);
            //Console.ReadKey();    //毎回の移動を確認する場合これを使ってください。
            TowerofHanoi(diskCount - 1, viaPole, toPole, fromPole);
        }
    }

    public static void ShowHanoi()
    {
        Console.Clear();
        Console.WriteLine(">>>>> Tower of Hanoi <<<<<\n");
        for(int i = tire - 1; i >= 0; i--)
        {
            Console.WriteLine(MakeDisk(pole[0].getDisk(i)) + MakeDisk(pole[1].getDisk(i)) + MakeDisk(pole[2].getDisk(i)));
        }
        string Horizen = new String('―', 60);
        Console.WriteLine(Horizen);
    }

    public static string MakeDisk(int num)
    {
        return String.Format("{0, 10}", disk[num]) + String.Format("{0, -10}", disk[num]);
    }
}

class Pole
{
    const int MaxTire = 10;
    int[] Disks;
    int Tire = 0;

    public Pole()
    {
        Disks = new int [MaxTire];
    }

    public Pole(int num)
    {
        if(num > MaxTire)
        {
            Disks = new int [MaxTire];
        }
        else
        {
            Disks = new int [num];
        }
    }

    public void push(int num)    //柱に盤を嵌める
    {
        Disks[Tire] = num;
        Tire++;
    }

    public int pop()            //柱から盤を抜き、盤数を返す(なければ0)
    {
        int val = 0;
        if(Tire > 0)
        {
            val = Disks[Tire - 1];
            Disks[Tire - 1] = 0;
            --Tire;
        }
        return val;
    }

    public int getDisk(int num)    //柱のnum番目の盤数を返す(なければ0)
    {
        return Disks[num];
    }
}
 

ps. 先般「ハマった」と書いた"Mirage"ですが、既に飽きました。「ハノイの塔」がそうならないことを祈ります。

 

前回思いついたことを、少し掘り下げて考えてみました。

 

1.参照したプログラムは何をしているのか?(その黙示の前提は?)

もっと簡略化した日本語C#版のプログラムを載せます。

//////////////////////////////////////////////////////////////////
// TowerOfHanoi01.cs
// Tower of Hanoi, a mathimatic game
// https://www.programmingalgorithms.com/algorithm/tower-of-hanoi/
//////////////////////////////////////////////////////////////////

using System;

class TowerOfHanoi
{
    public static void Main()
    {
        int num = 0;    //塔の盤の段数
        Console.Write("円盤の枚数(整数)を入力してください:");
        while(!int.TryParse(Console.ReadLine(), out num) || num <= 0)
        {
            Console.Write("\n円盤の枚数(整数)を入力してください:");
        }
        TowerofHanoi(num, 1, 2, 3);    //「ハノイの塔」関数(num段の柱を1→2へ移動)
        Console.ReadKey();    //コンソールを閉じさせない為
    }

    //「ハノイの塔」関数(num段の柱を1→2へ移動)
    public static void TowerofHanoi(int diskCount, int fromPole, int toPole, int viaPole) 
    {
        if(diskCount > 0)    //num段を1まで減らしてゆく
        {

           //「ハノイの塔」関数(num - 1段の柱を1→2へ移動)
            TowerofHanoi(diskCount - 1, fromPole, viaPole, toPole);
            //最下段の盤を1→2へ移動

            Console.WriteLine(diskCount + "番目の盤を" + fromPole + "番の柱から" + toPole + "番の柱へ移動します。");
            //「ハノイの塔」関数(num - 1段の柱を3→2へ移動)

            TowerofHanoi(diskCount - 1, viaPole, toPole, fromPole)   //num-  1段の「ハノイの塔」関数、柱は3→2へ移動
        }
    }
}

 

いかがでしょう?コメントを総合すると、

「ハノイの塔」関数(num, 1→2) = 「ハノイの塔」関数(num - 1, 1→3へ移動)+ 「ハノイの塔」関数(最下段1段, 1→2へ移動)+「ハノイの塔」関数(num - 1, 3→2)

即ち「ハノイの塔を1→2へ移動させることは、まず最下段を除く塔をいったん退避させ、最下段を目的の柱に移し、更に塔を退避先から目的の柱(toPole)へ移動させる」ことに等しい、ということが分かります。また退避は1(fromPole)と3(viaPole)を交互に使い、往復させながら、最下段の盤を目的の柱へ積み上げてゆく」という動作を行っていることが分かります。

プログラムのポイントはこの「」動作に再帰を使っていることです。

因みに盤と動きの数を調べてみると、

盤数  動き  差分

1    1    1

2    3    2

3    7    4

4    15    8

となり、2の盤数の階乗の和(Σ 2^k(n, k = 0))になっていることが分かります。

 

2.必要なオブジェクト

(1)表示母体(コンソールを含む描画背景)

動作を視覚表現するための描画背景が必要であり、ウィンドウとピクチャーボックスを使うことになりそうです。ウィンドウの大きさから塔の高さ(=盤数)には自ずと制限が出てきます。まぁ、16段ぐらいにしておきましょうか?

 

(2)盤と盤数の取り扱い

盤はビットマップで表現することになりそうですね。幅64 x 高さ16のビットマップで左右1ドット(計2ドット)ずつ減らしてゆくと32 x 16が一番上の盤(他は余白)になりますが、もっと差を設けたいですね。幅を増やすべきでしょう。これら16個のビットマップはImageListで管理することが簡単そうです。また、このビットマップは座標のある「盤クラス」のオブジェクトで描画することになるでしょう。

 

(3)柱とスタック

「ハノイの塔」を見て最初に感じるのは、この盤を嵌めたり、抜いたりする動作が「先入れ後出し(First In Last Out-FILO)」というスタックになっていることです。(盤を嵌める→PUSH、盤を抜く→POP)「塔クラス」のオブジェクトを3つ作り、塔にどの盤オブジェクトが入っているのかわかるようにして描画するとよいかもしれません。

 

(4)メソッドとしての「盤遷移」、「盤移動」、「盤表示」(初期状態と初期化)

後は「ハノイの塔」オブジェクトの描画ですが、盤(ビットマップ)、背景(含む柱)に「塔クラス」オブジェクト→「盤クラス」オブジェクトで座標を伝え、描画してゆくことになります。移動する盤を描画したいなら、PUSH、POPを上下動のメソッド、左右移動のメソッドでまとめておくと「盤クラス」オブジェクトと柱を指定するだけでスルスルと動くような気がします。

 

(5)盤の移動手順

これは↑に書いている「ハノイの塔」関数の「最下段の盤を1→2へ移動」の所がPOP→PUSHになります。

 

まぁ、このように抽象的にプログラムの全体概要を煮詰めてゆくことがまず必要なあぷろろーちになるでしょうか?

 

ps. どうでもよいことですが、昨年見ていた「エルピス」の最後に流れる"Mirage"に結構はまり、今も聞きながら書いています。(昔から気に入ったら飽きるまで繰り返して聴く性格なんです...)いろいろなバージョンがあり、一通り聞いたのですが、長澤まさみさんが参加するバージョン(↑のリンク張ってるTVドラマの最後の奴)が一番好きかな?

 

皆さま、明けましておめでとうございます。本年も本ブログをよろしくお願いします。

 

さて、年末にECCSkeltonでGraphMakerを作りましたが、年が明けても(特に実用アプリは)ネタ切れ状態が続いてまして、ヒントをくれそうなのは長い歴史を持ち、幅広いジャンルのプログラムが書かれたVisual Basicだろうと眺めていたら、懐かしい

ハノイの塔(注)

注:詳細はこちらを参照

が目につきました。

 

早速当サイトで扱っているC++とC#のサンプルを私の環境(C++はbcc32c.exe+BatchGood、C#はcsc.exe+MSCompAss)で動くようにしてみます。

 

【C++】

//////////////////////////////////////////////////////////////////
// TowerOfHanoi.cpp
// Tower of Hanoi, a mathimatic game
// https://www.programmingalgorithms.com/algorithm/tower-of-hanoi/
//////////////////////////////////////////////////////////////////

#include    <stdio.h>
#include    <conio.h>    //getch()使用の為
#include    <stdlib>
#include    <iostream>

using namespace std;

static void TowerofHanoi(int diskCount, int fromPole, int toPole, int viaPole) {

    if (diskCount == 1)
        cout << "Move disk from pole " << fromPole << " to pole " << toPole << endl;
    else {
        TowerofHanoi(diskCount - 1, fromPole, viaPole, toPole);
        TowerofHanoi(1, fromPole, toPole, viaPole);
        TowerofHanoi(diskCount - 1, viaPole, toPole, fromPole);
    }
}

int main() {

    int num = 0;
    while(num <= 0) {
        cout << "円盤の枚数(整数)を入力してください(実数は整数に切り捨てられます):";
        cin >> num;
    }
    TowerofHanoi(num, 1, 2, 3);
    getch();    //コンソールが閉じるのを停止させる
    return 0L;
}
 

【C#】

//////////////////////////////////////////////////////////////////
// TowerOfHanoi.cs
// Tower of Hanoi, a mathimatic game
// https://www.programmingalgorithms.com/algorithm/tower-of-hanoi/
//////////////////////////////////////////////////////////////////

using System;

class TowerOfHanoi
{
    public static void Main()
    {
        int num = 0;
        Console.Write("円盤の枚数(整数)を入力してください:");
        while(!int.TryParse(Console.ReadLine(), out num) || num <= 0)
        {
            Console.Write("\n円盤の枚数(整数)を入力してください:");
        }
        TowerofHanoi(num, 1, 2, 3);
        Console.ReadKey();    //コンソールを閉じさせない為
    }

    public static void TowerofHanoi(int diskCount, int fromPole, int toPole, int viaPole)
    {
        if (diskCount == 1)
        {
            Console.WriteLine("Move disk from pole " + fromPole + " to pole " + toPole);
        }
        else
        {
            TowerofHanoi(diskCount - 1, fromPole, viaPole, toPole);
            TowerofHanoi(1, fromPole, toPole, viaPole);
            TowerofHanoi(diskCount - 1, viaPole, toPole, fromPole);
        }
    }
}
 

両者は「ハノイの塔」部分の関数を引用し、ほぼ同じ(注)ような形にしております。

注:C++では0以下はエラーになりますが、小数点付きの実数を入力しても切り捨てにして整数化が図られますが、C#ではエラーとなります。

 

コンソールプログラムなので英語で「盤をX番の柱からY番の柱へ移動("Move disk from pole X to pole Y")」と表示されるだけなので想像力を働かせなければなりませんが、盤の枚数を変えてみると「あー、このように動かしているんだ」ということはわかります。

 

今年の一発目はこの「ハノイの塔」をよりビジュアルにしてみるのもよいかもしれませんね。(問題はC++でやるか、C#でやるか、ですか。先般はGraphMakerをECCSkeltonで作ったので、またC#で書いてみるのも一興かもしれませんね。)

 

前回までですべてのECCSkeltonファイルを作成しました。

GraphMaker.rc

ResGraphMaker.h

GraphMaker.h

GraphMakerProc.h

GraphMaker.cpp

 

これらをBatchGoodでコンパイルしてみましょう。

(1)GraphMaker.rcGraphMaker.cppの2ファイルをBatchGood(未起動でも起動後でも構わない)に落とします。

(2)左上のツリービューにファイルが表示されたことを確認し、右上の「bcc32c-オプション 詳細」ボタンを押します。

(3)↓のように「コンパイルオプション詳細」ダイアログが出ますので、実行ファイル形式を「Windowsアプリケーション(Unicode)」(↓の画面の次の選択肢)を選びます。

(4)更に「インクルードパス」でECCSkelton.hのあるフォールダーを、「?」を押したら現れる「フォールダー参照」ダイアログで指定します。「最適化」、「警告表示」はお好みで選んでください。「呼び出し規約」はブランクのままにして、最後に「終了」ボタンを押します。

(5)メインダイアログ画面の「ツール-バッチファイルを作る」を選択するか、4番目のツールバーボタン(「▼|」みたいな奴)を押します。

(6)コンソールウィンドウが表示されますのでエラーがないか確認します。なければ、「exeファイルを実行しますか?」ダイアログボックスの「はい」ボタンを押してください。

(7)GraphMakerが立ち上がります。

 

では、早速「式の種類」を一次方程式から順に選択し「グラフ表示」ボタンを押していってください。

このように表示されるはずです。

 

一次方程式で遊んでみます。

描画してから右クリックによるポップアップメニューの「前景色」を選択すると再描画してくれます。

 

二次方程式もこの通り。

 

偉そうに書いていますが、最初に三次方程式で y = x³ だけでは面白くないので、y = 0の時に80,0と-80をxの解とするy = x(x-80)(x+80)(y = x³ - 6400x)を描画しようとしてa = 1、c = -6400にして描画してみました。すると、

「はぁ?」ですよね?実はこれは正しく描けているのですが、描画エリアが小さいので縦直線二本(実はx = 0に三本目がある)のように見えるのです。aの値を小さくして画面に収まるようにし、その値を(やはり調整して -3600 = 60 x -60)cに乗じてやると、

正しく描画されました。

 

いかがでしたか?家庭内で数学の方程式を使うことはまずないので久々の中学数学は脳をリフレッシュできたのでは無いでしょうか?昔を思い出して、様々な値を入れて遊んでみてください。また、学齢期のお子さんがいれば、パパ、ママが解説してあげてください。

 

なお、今回は単なる関数グラフの描画でしたが、このプログラムを改造して汎用グラフ表示DLLを作ってみたり、CSDIウィンドウにリストビューを貼り付けて、データをCSVにして読み込ませ、CPICBOXでグラフ表示すると簡易表計算・グラフ表示ソフトができますよ。「え"~、そんなんならExcelを使うよ!」といわないでください。プログラミングの楽しさは「なんでも自分で試行錯誤しながら作ってみる」楽しさなんですから。(また、市販ソフトがどのように動いているかの舞台裏を垣間見ることもできますよ。)

 

それでは最後に実際にグラフを作ったり、色を変更したりするコードをGraphMakerProc.hに書き込んでゆきます。

またこと元のコメントと追加の「//解説:」を見てください。またBCC2ECCの変更部分はにしておきます。

 

【GraphMakerProc.h】

//////////////////////////////////////////
// GraphMakerProc.h
// Copyright (c) 12/14/2022 by ECCSkelton
//////////////////////////////////////////

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

    //PICTUREBOXの初期化
    m_PicBox.Init(m_hWnd, IDC_GRAPH);    //ダイアログコントロールIDC_GRAPHにラップ
    RECT rec;                            //m_PicBoxのクライアントエリアサイズを取得
    GetClientRect(m_PicBox.m_hWnd, &rec);
    m_GraphW = rec.right - rec.left;    
//m_PicBoxクライアントエリアの幅(523)
    m_GraphH = rec.bottom - rec.top;    //m_PicBoxクライアントエリアの高さ(334)
    ClearGraph();                        //背景を黒、軸を反対色(白)で初期化する(ユーザー定義関数)
    m_PicBox.Color(12);                    //描画色を初期的に明るい緑色にする
    m_FrCol = GetCol();                    //初期描画色を記録
//解説:カスタムコントロール"PICTUREBOX"のメンバー変数m_PicBoxをCPICBOXクラスでラップし、必要な値を記録するとともに初期化します。

    //コンボボックスの初期化
    WCHAR expStr1[11] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x0020, 0x002B, 0x0020, 0x0062, 0x0000};
    WCHAR expStr2[21] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x00B2, 0x0020, 0x002B, 0x0020, 0x0062, 0x0078, 0x0020, 0x002B, 0x0020, 0x0063, 0x0000};
    WCHAR expStr3[23] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x00B3, 0x0020, 0x002B, 0x0020, 0x0062, 0x0078, 0x00B2, 0x0020, 0x002B, 0x0020, 0x0063, 0x0078, 0x0020, 0x002B, 0x0020, 0x0064, 0x0000};
    WCHAR expStr4[17] = {0x0079, 0x0020, 0x003D, 0x0020, 0x002B, 0x002D, 0x221A, 0x0028, 0x0061, 0x0078, 0x00B2, 0x0020, 0x002D, 0x0020, 0x0062, 0x0029, 0x0000};

//解説:ここが噂の式(↓)のUnicode文字列をコード入力しているところです。

    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"");    //未選択用-解説:これが結構重要です。
    //一次方程式(linear equation)
    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr1);
//    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = ax + b");
    //二次方程式(quadratic equation)
    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr2);
//    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = ax^2 + bx + c");
    //三次方程式(cubic equation)
    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr3);
//    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = ax^3 + bx^2 + cx + d");
    //円の方程式(equation of a circle)
    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr4);
//    SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = +-√(ax^2 - b)");
    SendItemMsg(IDC_COMBOBOX, CB_SETCURSEL, 0, (LPARAM)0);

//解説:もともとはSJISで保存しても文字化けが起こらない式を使っていましたが、味気なくてねぇ。

    //a - dの項入力エディットボックスをdisabledにする
    SendItemMsg(IDC_EDITA, EM_SETLIMITTEXT, 5, 0);
    EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), FALSE);
    SendItemMsg(IDC_EDITB, EM_SETLIMITTEXT, 5, 0);
    EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), FALSE);
    SendItemMsg(IDC_EDITC, EM_SETLIMITTEXT, 5, 0);
    EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
    SendItemMsg(IDC_EDITD, EM_SETLIMITTEXT, 5, 0);
    EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);

//解説:値入力用のエディットボックスの入力文字数を制限し、不活性化します。

    return TRUE;
}

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

    if(((NMHDR*)lParam)->code == WM_RBUTTONDOWN) {
        //ポップアップメニューを読みこむ
        HMENU hMenu = LoadMenuW(m_hInstance, L"IDM_POPUP");
        HMENU hPopupMenu = GetSubMenu(hMenu, 0);

        //ポップアップメニューの表示
        POINT pt;
        GetCursorPos(&pt);
        TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON,
                      pt.x, pt.y, 0, m_hWnd, NULL);

        //メニューリソースの開放
        DestroyMenu(hMenu);

//解説:このブログを書いていて、ロードした(メモリーを確保した)メニューを開放していないことを発見!直ちに修正しました。(最近こういうボケが多いなぁ。)なお、この右クリックポップアップメニューのコードは定番でBCCMakerから借りてきました。

    }

    return TRUE;
}

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

    if(MessageBoxW(m_hWnd, L"終了しますか", L"終了確認",
                    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::OnClear() {

    SetCol(m_BkCol);        
//前景色から背景色へ変更
    ClearGraph();            //背景を背景色で塗りつぶす
    SetCol(m_FrCol);        //前景色へ戻す
    return TRUE;
}

//解説:これは手書きで追加した画面をクリアする処理です。処理はコメントのとおりです。


bool CMyWnd::OnBackCol() {

    m_PicBox.Color(-1);        
//カラーダイアログを使って色選択を行う(キャンセルの場合は0 == 黒)
    m_BkCol = GetCol();        //新設定色を記録
    OnClear();                //消去して再描画
    if(m_Selection)            //式(グラフ)が選択されていれば再描画
        OnShowgraph();
    return TRUE;

}

//解説:これは手書きで追加した背景色設定処理です。処理はコメントのとおりです。


bool CMyWnd::OnFrontCol() {

    m_PicBox.Color(-1);        
//カラーダイアログを使って色選択を行う(キャンセルの場合は0 == 黒)
    m_FrCol = GetCol();        //新設定色を記録
    if(m_Selection)            //式(グラフ)が選択されていれば再描画
        OnShowgraph();
    return TRUE;
}

//解説:これは手書きで追加した前景色設定処理です。処理はコメントのとおりです。


bool CMyWnd::OnCombobox(WPARAM wParam) {

    if(HIWORD(wParam) == CBN_SELCHANGE) {

//解説:コンボボックスの選択に変更があった時だけ処理をします
        m_Selection = SendItemMsg(IDC_COMBOBOX, CB_GETCURSEL, 0, 0);
//解説:新しい選択を取得します
        switch(m_Selection) {
        case 0:    
//未選択状態(全ての項入力ボックスを不能にする)
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), FALSE);
            SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), FALSE);
            SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
            SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
            SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"");
            break;

//解説:未選択の場合はすべての項入力ボックスをブランクにし、入力不能にします
        case 1:    //一次方程式
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
            SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"1");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
            SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"0");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
            SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
            SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"");
            break;
        case 2:    
//二次方程式
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
            SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"0.01");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
            SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"0");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), TRUE);
            SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"-144");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
            SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"");
            break;
        case 3:    
//三次方程式
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
            SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"0.001");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
            SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"0");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), TRUE);
            SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"0");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), TRUE);
            SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"0");
            break;
        case 4:    
//円の方程式
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
            SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"1");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
            SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"6400");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
            SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"0");
            EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
            SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"0");
            break;

//解説:式を選択した場合は関連のある項だけ入力を許し、(そのままグラフが描けるように)入力ボックスにサンプルの値を入れています。
        }
        return TRUE;
    }
    return FALSE;

}

bool CMyWnd::OnShowgraph() {

//解説:ここがグラフを描画する処理です。
    //変数宣言
    WCHAR str[6];
    double a, b, c, d;                
//項の値(倍精度
    static int sx, sy, ex, ey;        
//始点と終点
//解説:最初始点と終点はPOINTでメンバー変数にしようと考えたのですが、CANVASがintで処理するので変更しました。
    //aとbの項目データを読み取る
//解説:グラフ描画では必ずaとbを使うのでこれらは無条件に取得します。
    SendItemMsg(IDC_EDITA, WM_GETTEXT, 6, (LPARAM)str);
//解説:入力最大文字数が5文字なので、NULL終端を入れて6文字までを取得します。
    if(!*str) {
        MessageBoxW(m_hWnd, L"項aが未入力です", L"エラー", MB_OK | MB_ICONERROR);
        return FALSE;

//解説:ブランクの場合のエラー処理です。
    }
    a = stod(str);

//解説:取得された文字列を倍精度実数に変更するstod関数を使い、IDC_EDITAエディットボックスに対応したaに代入します。
    if(a == 0) {
        MessageBoxW(m_hWnd, L"項aがゼロです", L"エラー", MB_OK | MB_ICONERROR);
        return FALSE;

//解説:aがゼロだと話にならないのでaだけエラー処理を行います。
    }
    SendItemMsg(IDC_EDITB, WM_GETTEXT, 6, (LPARAM)str);
    if(!*str) {
        MessageBoxW(m_hWnd, L"項bが未入力です", L"エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    b = stod(str);

//解説:IDC_EDITBも同じように処理します。
    //m_PicBoxクライアントエリアを左から右へ描画
    for(int i = 0, sx = ex = 0; i < m_GraphW; i++) {
//解説:描画するm_PicBoxのクライアントエリアを左から右にかけて整数X座標を動かします。これに対応する整数Y座標を取得して、「前回の座標と、今回の座標を線で描画」します。(注:「点で描画」してもよいのですが、Y座標が大きく異なると連続性がなくなるので線にしました。)
        double x = (double)(ex - m_GraphW / 2);
//解説:描画するm_PicBoxのクライアントエリア整数X座標を倍精度実数の仮想X座標に変換します。
        double y;
        ex = i;

//解説:倍精度実数の仮想Y座標変数を宣言します。また「今回の整数X座標ex」にiを代入します。

        switch(m_Selection) {
//解説:選択式(m_Selection)に応じてグラフを描画します。

       case 0:
            MessageBoxW(m_hWnd, L"式が選択されていません", L"エラー", MB_OK | MB_ICONERROR);
            return FALSE;

//解説:未選択の場合のエラー処理です。

        case 1:
            
//一次方程式グラフ描画
            y = a * x + b;                        //方程式計算
            ey = (int)-y + m_GraphH / 2;        //上下反転
//解説:仮想座標では上、右へ大きくなるのですが、m_PicBoxの整数座標は下、右へ大きくなるので反転する必要があります。

            if(!sx)
                sy = ey;
            m_PicBox.Line(sx, sy, ex, ey);        
//始点-終点線の描画
            break;

//解説:指定した式のとおりに倍精度の仮想座標で計算を行い、それをm_PicBoxの整数座標に変換して「前回終点の始点と今回終点間の線」を描画します。

        case 2:
            
//二次方程式グラフ描画
            SendItemMsg(IDC_EDITC, WM_GETTEXT, 6, (LPARAM)str);
            if(!*str) {
                MessageBoxW(m_hWnd, L"項cが未入力です", L"エラー", MB_OK | MB_ICONERROR);
                return FALSE;
            }
            c = stod(str);

//解説:二次方程式ではcまで使います。

            y = a * pow(x, 2.0) + b * x + c;    //方程式計算
//解説:べき乗計算にはpow(底、指数)関数を使います。

            ey = (int)-y + m_GraphH / 2;        //上下反転
            if(!i)
                sy = ey;
            m_PicBox.Line(sx, sy, ex, ey);        
//始点-終点線の描画
            break;
        case 3:
            
//三次方程式グラフ描画
            SendItemMsg(IDC_EDITC, WM_GETTEXT, 6, (LPARAM)str);
            if(!*str) {
                MessageBoxW(m_hWnd, L"項cが未入力です", L"エラー", MB_OK | MB_ICONERROR);
                return FALSE;
            }
            c = stod(str);
            SendItemMsg(IDC_EDITD, WM_GETTEXT, 6, (LPARAM)str);
            if(!*str) {
                MessageBoxW(m_hWnd, L"項dが未入力です", L"エラー", MB_OK | MB_ICONERROR);
                return FALSE;
            }
            d = stod(str);

//解説:三次方程式ではcに加え、dまで使います。

            y = a * pow(x, 3.0) + b * pow(x, 2.0) + c * x + d;    //方程式計算

//解説:pow(底、指数)関数が大活躍!

            ey = (int)-y + m_GraphH / 2;        //上下反転
            if(!ex)
                sy = ey;
            m_PicBox.Line(sx, sy, ex, ey);        
//始点-終点線の描画
            break;
        case 4:
            
//円の方程式グラフ描画(+と-の解を一緒に描画)-解説:式から中心は常に(0, 0)になります。
            double rad = sqrt(b);
//解説:最初に半径(radius)radを求めておきます。

            if(x >= -rad && x <= rad) {

                y = sqrt(abs(a * pow(x, 2.0) - b));    //方程式計算

//解説:squrt()は平方根を求める関数です。

                ey = -(int)y + m_GraphH / 2;        //上下反転(y)
                if(x == -rad)
                    sy = ey;

//解説:+と-の解を一緒に描画します

                m_PicBox.Line(sx, sy, ex, ey);        //上弦の描画
                m_PicBox.Line(sx, sy - sy * 2 + m_GraphH,
                    ex, ey - ey * 2 + m_GraphH);    
//下弦の描画
            }
            break;
        }
        sx = ex;    
//今回終点を次回の始点にして一つXを進める
        sy = ey;    //今回終点を次回の始点にして一つYを進める
    }
    return TRUE;
}


bool CMyWnd::OnIdok() {

    SendMsg(WM_CLOSE, 0, 0);
//解説:定番のWM_CLOSEメッセージの発信です。
    return TRUE;
}

//ユーザー定義関数
COLORREF CMyWnd::GetCol() {

    return m_PicBox.m_Color;

//解説:CPICBOXはCANVASを持っており、そのメンバー変数m_Colorの値を返します。
}

void CMyWnd::SetCol(COLORREF col) {

    m_PicBox.m_Color = col;

//解説:CPICBOXのCANVASのメンバー変数m_Colorの値を設定します。
}

void CMyWnd::ClearGraph() {

    m_PicBox.BrSelection(1);
    m_PicBox.Clear();
    COLORREF ContraCol = m_BkCol ^ 0x00FFFFFF;   
//背景色(COLORREF-下3バイトがRGB)の排他的論理和を取得
    SetCol(ContraCol);
    m_PicBox.Line(m_GraphW / 2, 0, m_GraphW / 2, m_GraphH);
    m_PicBox.Line(0, m_GraphH / 2, m_GraphW, m_GraphH / 2);
    SetCol(m_FrCol);
}

//解説:これらは共通の処理をユーザー定義関数にしたものです。ピクチャーボックス(の中のCANVAS)は簡単な16色選択ができますが、それ以外は色選択ダイアログを使って色指定もできます。色情報は内部的にCOLORREF変数(m_Color)で持っているのでそれを取得したり、設定したりすることをC#のようにGet、Setで行っています。また画面消去(ClearGraph関数)は選択色(m_Color)で塗りつぶすだけではなく、前景色で「背景色の反対色(ContraCol)」でx、y座標軸を描画し、最後に記録した前景色に戻します。

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

 

次回は作成したECCSkeltonコードをコンパイルして動作を確認します。

 

次はGraphMaker.hに行きましょう。

GraphMakerは、中坊の数学程度の関数グラフを描画することを考えていますので、仕様として、

(1)一次、二次、三次(は中学でやっていなかったっけな?まっ、いいか)と円の方程式を入れましょう。
"y = ax + b"
"y = ax² + bx + c"
"y = ax³ + bx² + cx + d"
"y = +-√(ax² - b)"

(2)これらの選択は式を文字列(上付きとルート記号がありUnicodeとなる)としてコンボボックスへ入れて行いましょう。
(3)また選択された式の項のa~dの値はエディットボックスに入力してもらいますが、不要なエディットボックスはdisabledにしましょうか。
(4)更に項の値は少数以下も使えるように倍精度実数(double)にしましょう。
(5)これらの入力が終わったら、IDC_SHOWGRAPHボタンでグラフを描画し、IDOKボタンでプログラムを終了させます。
(6)グラフの描画は"PICTUREBOX"コントロール()のIDC_GRAPHで行いますので、IDC_GRAPHの画面クリア、前景色(グラフの色)と背景色も変更できるようにしたいですね。これは右クリックのポップアップメニューで行うことにしましょう。

:PICTUREBOXはBCCSkelton|ECCSkeltonのCANVASクラスを利用するコントロールです。


と定めます。

では、早速BCCFormで作らなかったポップアップメニューを手書きで追加します。
私はこのような場合、過去に作ったプログラムのコードを持ってきて貼り付けるようにします。丁度BCCMakerで独立したポップアップメニューを使っていたのでそのコードを持ってきます。

【GraphMaker.rc】
//----------------------
// ポップアップメニュー
//----------------------
IDM_POPUP MENU DISCARDABLE
{
    POPUP L"ポップアップ"
    {
        MENUITEM L"消去", IDC_CLEAR
        MENUITEM SEPARATOR
        MENUITEM L"背景色", IDC_BACKCOL
        MENUITEM L"前景色", IDC_FRONTCOL
    }
}


【ResGraphMaker.h】
//---------------------
//  メニューリソース
//---------------------
#define    IDC_CLEAR          200
#define    IDC_BACKCOL      201
#define    IDC_FRONTCOL    202

この3つのメニューアイテムをGraphMaker.hに追加してやる必要があります。

では、最終的にGraphMaker.hにどのように手を入れたかを解説します。

【GraphMaker.h】
//解説:赤字部分はすべてBCC2ECCが書き換えています。

//////////////////////////////////////////
// GraphMaker.h
//
Copyright (c) 12/14/2022 by ECCSkelton
//////////////////////////////////////////
//
ECCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "ECCSkelton.h"
//リソースIDのヘッダー
#include    "ResGraphMaker.h"


/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(
WCHAR* UName) : CDLG(UName) {}
    //メンバー変数
    CPICBOX m_PicBox;        
//PictureBoxの作成
    int m_Selection = 0;    //選択された式の番号(順)
    int m_GraphW = 0;        //m_PicBoxクライアントエリアの幅
    int m_GraphH = 0;        //m_PicBoxクライアントエリアの高さ
    COLORREF m_FrCol;        //描画(前景)色
    COLORREF m_BkCol = 0;    //背景色
//解説:↑の「メンバー変数」はGraphMaker用の手書き追加部分です。その目的はコメントのとおりです。
    //メニュー項目、ダイアログコントロール関連
    bool OnClear();
    bool OnBackCol();
    bool OnFrontCol();

//解説:これらがポップアップメニューの手書き追加部分です。
    bool OnCombobox(WPARAM);    //解説:「式の選択」で使うコンボボックスの選択処理です
    bool OnShowgraph();    //解説:グラフ描画処理です
    bool OnIdok();    //解説:終了処理です
    //ウィンドウメッセージ関連
    CMDTABLE                //OnCommand()関数宣言
    bool OnInit(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);

    //ユーザー定義関数
    COLORREF GetCol();
    void SetCol(COLORREF);
    void ClearGraph();

//解説:これらは描画処理で使うユーザー定義関数(手書き追加)です。
};

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

BEGIN_CMDTABLE(CMyWnd)    //クラス名がCMyWndではない場合、クラス名、テーブルを適宜マニュアルで修正してください
//BEGIN_MODELESSDLGMSG(ModelessProc, GraphMaker)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている    //解説:BCCSkeltonのコードをコメントアウトしています。
    //メニュー項目、ダイアログコントロール関連
    ON(IDC_CLEAR, OnClear())
    ON(IDC_BACKCOL, OnBackCol())
    ON(IDC_FRONTCOL, OnFrontCol())

//解説:これらがポップアップメニューの手書き追加部分です。
    ON(IDC_COMBOBOX, OnCombobox(wParam))
    ON(IDC_SHOWGRAPH, OnShowgraph())
    ON(IDOK, OnIdok())

//解説:BCCSkeltonのコードを書き換えています。

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

//解説:BCCSkeltonのコードをコメントアウトしています。

END_CMDTABLE

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

//解説:SkeltonWizardで「コモンダイアログ」にチェックを入れませんでしたが、あとで気が変わって色選択ダイアログを使うことにしたので手書きで追加しました。

 

これでGraphMakerの宣言や定義が完了しましたので、次回以降は宣言、定義された関数の実装や変数の利用を最後に残されたGraphMakerProc.hで解説してゆきます。

 

前回Unicodeベースのスケルトンができたので「そのままコンパイルして」()などと書きましたが、それは大嘘です。(「そのままコンパイル」すると、カスタムコントロールの代わりにLABELコントロールが表示されます。)

:前回の該当部分引用。

後は、ResGraphMaker.hファイルだけ残し、ANSI用スケルトンファイルは削除していただいて結構です。そして新しくコンバートされたファイルの"_ECC"を削除して次のようなスケルトンファイルにします。

GraphMaker.rc

ResGraphMaker.h

GraphMaker.h

GraphMakerProc.h

GraphMaker.cpp

この段階で一度

(1)BatchGoodを起動し、

(2)コンパイルオプションをユニコード用のウィンドウプログラムにして、且つ

(3)「インクルードパス」にECCSkelton.hのあるパスを指定して

コンパイルします。

 

現実には"PICTUREBOX"というカスタムコントロールがあるので、ちょっといじらなければなりません。

【GraphMaker.rc】

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 363, 303
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION
L"GraphMaker"
FONT 8,
L"MS 明朝"
{
 CONTROL
L"", IDC_LABEL, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 51, 351, 246, WS_EX_CLIENTEDGE
 CONTROL
L"", IDC_COMBOBOX, L"COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_SORT | CBS_DROPDOWNLIST | WS_VSCROLL, 54, 9, 231, 12
 CONTROL
L"式の種類", IDC_KIND, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 9, 12, 39, 12
 CONTROL
L"項aの値", IDC_A, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 9, 33, 39, 12
 CONTROL
L"", IDC_EDITA, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 54, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL
L"項bの値", IDC_B, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 78, 33, 39, 12
 CONTROL
L"", IDC_EDITB, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 123, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL L"項cの値", IDC_C, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 150, 33, 39, 12
 CONTROL
L"", IDC_EDITC, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 195, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL
L"項dの値", IDC_D, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 219, 33, 39, 12
 CONTROL
L"", IDC_EDITD, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 264, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL
L"グラフ表示", IDC_SHOWGRAPH, L"BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 294, 7, 60, 15
 CONTROL
L"終了", IDOK, L"BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 294, 30, 60, 15
}

//--------------------------
// イメージ(IDI_ICO)
//--------------------------
IDI_ICO    ICON    DISCARDABLE    "GraphMaker.ico"

 

.rcファイルはほとんどANSI文字列(char*)をUnicode文字列(WCHAR*)にかえるだけ(ファイル指定部分は除く)なのですが、カスタムコントロールのIDとウィンドウクラスを変え、ポップアップメニューを手書きで加え(次回詳報)、あと細かなサイズ変更や順位変更を行い次の通りとしました。

 

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 363, 303
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION L"GraphMaker"
FONT 8, L"MS 明朝"
{
 CONTROL L"", IDC_COMBOBOX, L"COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 54, 9, 236, 72
 CONTROL L"", IDC_EDITA, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 54, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"", IDC_EDITB, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 123, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"", IDC_EDITC, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 195, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"", IDC_EDITD, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 264, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"グラフ表示", IDC_SHOWGRAPH, L"BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 297, 7, 60, 15
 CONTROL L"終了", IDOK, L"BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 297, 30, 60, 15
 CONTROL L"",
IDC_GRAPH, L"PICTUREBOX", WS_CHILD | WS_VISIBLE, 6, 51, 351, 246, WS_EX_CLIENTEDGE
 CONTROL L"式の種類", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 9, 12, 39, 12
 CONTROL L"項aの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 9, 33, 39, 12
 CONTROL L"項bの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 78, 33, 39, 12
 CONTROL L"項cの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 150, 33, 39, 12
 CONTROL L"項dの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 219, 33, 39, 12
 }


//----------------------
// ポップアップメニュー
//----------------------
IDM_POPUP MENU DISCARDABLE
{
    POPUP L"ポップアップ"
    {
        MENUITEM L"消去", IDC_CLEAR
        MENUITEM SEPARATOR
        MENUITEM L"背景色", IDC_BACKCOL
        MENUITEM L"前景色", IDC_FRONTCOL
    }
}


//--------------------------
// イメージ(IDI_ICO)
//--------------------------
IDI_ICO    ICON    DISCARDABLE    "GraphMaker.ico"

 

【GraphMaker.cpp】

//////////////////////////////////////////
// GraphMaker.cpp
//
Copyright (c) 12/19/2022 by ECCSkelton
//////////////////////////////////////////
#include    "GraphMaker.h"
#include    "GraphMakerProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI
wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LP
WSTR lpCmdLine, int nCmdShow) {

    //2重起動防止
    if(!GraphMaker.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)
        //HWND hWnd = FindWindow(
L"MainWnd", L"GraphMaker");
        //if(IsIconic(hWnd))
        //    ShowWindow(hWnd, SW_RESTORE);
        //SetForegroundWindow(hWnd);
        //SendMessage(hWnd, WM_COMMAND, IDM_OPEN, 0);
        return 0L;
    }

    //モードレスダイアログを作成Create(hParent, DlgName
, DlgProc);    //このDlgProcはECCSkeltonでは使わない
    if(!GraphMaker.Create(NULL, hInstance, L"IDD_MAIN"))
        return 0L;

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

 

これは二重起動防止部分をどうするか決めて(私は禁止処理としました)、プログラムが起動してからメインダイアログを作る(Create関数)までにカスタムコントロールの"PICTUREBOX"を登録してやる必要があります。

 

//////////////////////////////////////////
// GraphMaker.cpp
//Copyright (c) 12/14/2022 by ECCSkelton
//////////////////////////////////////////
#include    "GraphMaker.h"
#include    "GraphMakerProc.h"

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

    //2重起動防止
    if(!GraphMaker.IsOnlyOne()) {
        HWND hWnd = FindWindow(L"MainWnd", L"GraphMaker");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

  
 //CPICBOX(ウィンドウ)クラスを登録する
    if(!GraphMaker.m_PicBox.Register(hInstance))    //PICTUREBOXコントロールはメインダイアログインスタンスGraphMakerのメンバー変数にしているので、"GraphMaker.m_PicBox"でアクセスし、そのメンバー関数Registerを呼び出しているのでこういう表記になります。

        MessageBoxW(NULL, L"PictureBoxが登録できませんでした", L"警告", MB_OK | MB_ICONSTOP);

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

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

 

ここまでして前回のUnicodeベースのスケルトンが「そのままコンパイル」できます。

 

さて、ここまでは単なるスケルトンだけでしたが、次回以降はグラフ作成機能を備えるために何をしているか、まずGraphMaker.hファイルを解説し、その後その実装内容をGraphMakerProc.hファイルの解説で見てゆきます。

 

前回は作成したメインダイアログのリソースファイル(GraphMaker.rcResGraphMaker.h)をつかったANSI(というかBCCSkelton)用のスケルトンプログラムを作成しましたが、その「出来立てのほやほやのファイル」

GraphMaker..bdp

GraphMaker.h

GraphMakerProc.h

GraphMaker.cpp

を使って次にUnicode(というかECCSkelton)用のスケルトンプログラムを作成してみましょう。

 

まず、サンプルプログラムに入っているBCC2ECCというプログラムを起動します。(

:ANSI版がSampleBCCSkeltonフォールダーに、Unicode版がECCSkeltonSampleフォールダーに入っています。

 

「プロジェクト名」の右端にある「選択」ボタンを押して、作成されたGraphMaker.bdpファイルを選択します。(すぐ後に説明文のメッセージダイアログが出ますのでよく読んでください。)後は、単に「変換」ボタンを押すだけです。(作成したスケルトンファイルはまだ修正していないので「特定関数も対象にする」はブランクのままで結構です。)

 

すると、フォールダーの中に...

GraphMaker_ECC.h

GraphMakerProc_ECC.h

GraphMaker_ECC.cpp

GraphMaker_ECC.rc

というファイルが新たに作成されます。

 

後は、ResGraphMaker.hファイルだけ残し、ANSI用スケルトンファイルは削除していただいて結構です。そして新しくコンバートされたファイルの"_ECC"を削除して次のようなスケルトンファイルにします。

GraphMaker.rc

ResGraphMaker.h

GraphMaker.h

GraphMakerProc.h

GraphMaker.cpp

GraphMaker.rc

 

この段階で一度

(1)BatchGoodを起動し、

(2)コンパイルオプションをユニコード用のウィンドウプログラムにして、且つ

(3)「インクルードパス」にECCSkelton.hのあるパスを指定して

コンパイルします。

↓のイメージは旧いものなのでANSI用のウィンドウプログラムを選択していますが、その次の「Windowsアプリケーション(Unicode)」を選択してください。インクルードパスは「?」ボタンを押すと「フォールダーの参照」ダイアログが現れますので、それで選択してください。

 

これでUnicodeベースのダイアログプログラムのスケルトンの完成です。(「終了」を押しても終了しないので、システムの「X」ボタンで終了させてください。)

 

次回以降は作成されたECCSkelton用のスケルトンファイルと手作業で仕上げた最終ファイルをひとつづつ説明してゆきます。

 

前回まででGraphMakerのリソースができましたので、GraphMaker.rcを使ってスケルトンプログラムを作りましょう。

 

まず、GraphMaker.rcファイルをドラッグしてSkeltonWizard.exeにドロップしてください。(

:これは"(ファイルパス)SkeltonWizard.exe (ファイルパス)GraphMaker.rc"というコマンドラインを与えることと等価です。

 

すると、ウィザードの1ページ目が出ますので、「ウィンドウタイプ」を「ダイアログ」にし、その際にドロップダウンリストに表示される「IDD_MAIN」を選択し、「アイコン選択」でドロップダウンリストの「IDI_ICON」を選択します。そして「次に」ボタンを押しましょう。(今回は簡単なので迷わないと思います。)

次にウィザードの2ページ目が出ますが、ツールバーもステータスバーも使わないので、そのまま「次に」ボタンを押します。

するとウィザードの3ページ目が出ますので、今回はウィンドウメッセージからWM_INITDIALOG、WM_NOTIFY、WM_CLOSEを、またメニュー・コントロールからコンボボックス(IDC_COMBOBOX)、ボタン(IDC_SHOWGRAPH、IDOK)を選択します。(

:今後何をどうするか、まだはっきりしない場合は「総て選択」を押して、あとから不要な関数を(プロジェクト名).h、(プロジェクト名)Proc.hファイルから削除し手も大丈夫です。(手作業で追加するよりも楽でしょう。)

 

最後にウィザードの4ページ目で内容を確認して「完了」を押します。(

:「WYSWYG」と「CPPファイルを書き換えない」のチェックボックスはそのままにしておいてください。前者はスケルトンのメニュ・コントロールの関数の部分をBCFエディターで呼び出す機能、後者は一度作ったファイルがあり、再度SkeltonWizardを起動させた場合に(手を入れた).cppファイルを上書きされないようにするためのものです。なお、既にプログラムファイルが存在していてSkeltonWizardを起動した際に前のファイルに同じ関数が発見されると「書き換えますか?」という注意確認が出ます。

 

これでGraphMaker.rc、ResGraphMaker.hと同じフォールダーに、

GraphMaker..bdp

GraphMaker.h

GraphMakerProc.h

GraphMaker.cpp

というファイルが作成されます。今回はANSIベースではなく、Unicodeベースで開発するので、これらBCCSkeltonファイルをECCSkeltonファイルへ変更する必要があります。これについては次回、BCC2ECCプログラムを使ったコンバートプロセスと結果をご紹介します。

 

ps. さて、少しお恥ずかしい話をさせてください。↑のようにして作ったファイルのうち、GraphMaker.hについて実は問題があります。先ほど「WM_INITDIALOG、WM_NOTIFY、WM_CLOSE」メッセージの処理を選択しましたが、コメントに記載されているようにWM_INITDIALOGとWM_CLOSEはSkeltyonWizard.hで処理をしており、BEGIN_MODELESSDLGMSGEND_DLGMSGでユーザーが記載する必要はありません。(ON_INIT~の行がないにも拘わらず、OnInit()関数の宣言があることにご注意ください。ON_CLOSE~についても同様に処理すべきところ、WM_CLOSEメッセージを選択するとOnClose()関数の宣言のみならず、ON_CLOSE~の行が追加されてしまうことが問題となります。これはコード的に言うとWM_CLOSEメッセージを2度受け付けることになる為、(例えば)終了確認ダイアログを使うと「2度終了確認ダイアログが現れる」結果になります。

従って、このような症状が出たら、ON_CLOSE~の行を確認し、それがあれば削除するか、コメントアウトしてください。

なお、ECCSkeltonはウィンドウメッセージテーブルを持っていないので、変換される場合はBCC2ECCがコメントアウトしてくれます。(BCCForm、SkeltopnWizardは事故でソースコードを失っているので修正できません。悪しからず、ご了承願います。)

 

//////////////////////////////////////////
// GraphMaker.h
// Copyright (c) 12/19/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResGraphMaker.h"

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

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


BEGIN_MODELESSDLGMSG(ModelessProc, GraphMaker)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(GraphMaker, IDC_COMBOBOX, OnCombobox())
    ON_COMMAND(GraphMaker, IDC_SHOWGRAPH, OnShowgraph())
    ON_COMMAND(GraphMaker, IDOK, OnIdok())
    //ウィンドウメッセージ関連
  
 //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_NOTIFY(GraphMaker)
    
ON_CLOSE(GraphMaker)    //この行は不要。削除、またはコメントアウトする。
    ON_DESTROY(GraphMaker)
END_DLGMSG
 

GraphMakerのメインウィンドウはダイアログにしましたので、BCCFormで作成します。

 

今回、いつもと異なるのはカスタムコントロールの"PICTUREBOX"()を使うことです。どう違うのかというと、カスタムコントロールというのは「まだ存在していないコントロールである」ということで、即ち「BCCFormでは作れない」ということを意味します。

:このコントロールをラップする、BCCSkelton | ECCSkeltonの「CPICBOXクラス」で定めるウィンドウクラス名です。

 

「???ではどうするの?」と思われるでしょうが、適当にラベル(Static Control)でも身代わりに立ててセットします。ついでにクライアントエッジなどを効かしておきましょう。

 

以下はプロトタイプのメインダイアログ(IDD_MAIN)で、式の選択を行うコンボボックス、入力用のエディットボックス、それらの説明表示用のラベルと、グラフ表示や終了用のボタンを貼り付けます。

 

【GraphMaker.rcのプロトタイプ】

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 363, 303
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "GraphMaker"
FONT 8, "MS 明朝"
{
 CONTROL "",
IDC_LABEL, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 51, 351, 246, WS_EX_CLIENTEDGE
 CONTROL "", IDC_COMBOBOX, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_SORT | CBS_DROPDOWNLIST | WS_VSCROLL, 54, 9, 231, 12
 CONTROL "式の種類", IDC_KIND, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 9, 12, 39, 12
 CONTROL "項aの値", IDC_A, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 9, 33, 39, 12
 CONTROL "", IDC_EDITA, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 54, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL "項bの値", IDC_B, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 78, 33, 39, 12
 CONTROL "", IDC_EDITB, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 123, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL "項cの値", IDC_C, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 150, 33, 39, 12
 CONTROL "", IDC_EDITC, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 195, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL "項dの値", IDC_D, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 219, 33, 39, 12
 CONTROL "", IDC_EDITD, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 264, 30, 21, 12, WS_EX_CLIENTEDGE
 CONTROL "グラフ表示", IDC_SHOWGRAPH, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 294, 7, 60, 15
 CONTROL "終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 294, 30, 60, 15
}

//--------------------------
// イメージ(IDI_ICO)
//--------------------------
IDI_ICO    ICON    DISCARDABLE    "GraphMaker.ico"

 

後で↑の赤字部分を IDC_GRAPH, "PICTUREBOX"に変更し大体次のような見栄えになりました。(ここではすでにPICTUREBOXにして、初期設定で背景を黒で塗りつぶしています。)

 

BCCFormを使う際の「私のコツ」なんですが、(マウスを使ったレイアウトは正確性に限界があり、結局最後は「あーでもない、こーでもない」と何度かコンパイルしてソースコードでサイズ、位置の1、2ドットの微調整をおこなうことになるので)あまり最初は細部にこだわらず、必要なコントロールを配し、並べるコントロールは右クリックのポップアップメニューを使って「コントロールの配置-複数コントロールの配置」でレイアウトを定め、(大雑把でも)「最終形」を作ることです。(注)

:特にカスタムコントロールの"PICTUREBOX"を使っているので、"LABEL"から書き換えた後ではBCCFormでの編集は不可能になることにご注意ください。(↑で書いた通り、「BCCFormでは作れない」。)

 

因みにBCC2ECCを使ってUnicode対応とした最終形は以下の通り。エディットコントロールの幅を4ドット拡げ、それに合わせてコンボボックスの幅も調整したり、細部は最終的にコードを修正して仕上げを行います。

【GraphMaker.rcの最終形】

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 363, 303
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION L"GraphMaker"
FONT 8, L"MS 明朝"
{
 CONTROL L"", IDC_COMBOBOX, L"COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 54, 9, 236, 72
 CONTROL L"", IDC_EDITA, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 54, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"", IDC_EDITB, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 123, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"", IDC_EDITC, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 195, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"", IDC_EDITD, L"EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_CENTER, 264, 30, 26, 12, WS_EX_CLIENTEDGE
 CONTROL L"グラフ表示", IDC_SHOWGRAPH, L"BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 297, 7, 60, 15
 CONTROL L"終了", IDOK, L"BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 297, 30, 60, 15
 CONTROL L"",
IDC_GRAPH, L"PICTUREBOX", WS_CHILD | WS_VISIBLE, 6, 51, 351, 246, WS_EX_CLIENTEDGE
 CONTROL L"式の種類", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 9, 12, 39, 12
 CONTROL L"項aの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 9, 33, 39, 12
 CONTROL L"項bの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 78, 33, 39, 12
 CONTROL L"項cの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 150, 33, 39, 12
 CONTROL L"項dの値", 0, L"STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_RIGHT, 219, 33, 39, 12
 }   
//ラベルは使わないので皆値を0にし、コントロールの順位は最終的に手作業で変更しています。

//----------------------
// ポップアップメニュー
(後で右クリックのポップアップメニューが欲しくなり手作業で追加しました。)
//----------------------
IDM_POPUP MENU DISCARDABLE
{
    POPUP L"ポップアップ"
    {
        MENUITEM L"消去", IDC_CLEAR
        MENUITEM SEPARATOR
        MENUITEM L"背景色", IDC_BACKCOL
        MENUITEM L"前景色", IDC_FRONTCOL
    }
}

//--------------------------
// イメージ(IDI_ICO)
//--------------------------
IDI_ICO    ICON    DISCARDABLE    "GraphMaker.ico"

 

このようにして、BCCFormでGraphMaker.rcとResGraphMaker.hの二つのファイルを作製したら、次はSkeltonWizardを使ってスケルトンプログラムを作成します。