さて、前回で最終仕様も決まって使えるようになったので、プログラムコードを末尾に掲載します。

 

(1)このプログラムは、検査履歴データを読み込んでMS Chartコントロールで棒グラフを表示するDemPreChartクラス(ウィンドウ)と、36のボタン入力で検査するDemAppクラス、およびエントリーポイントだけのLauncherクラスで構成されています。

 

(2)プログラムの流れは、

 

 (a)LauncherクラスインスタンスがDemAppクラスインスタンスを実行してプログラムが開始され、

 (b)DemAppウィンドウ生成時、DemPreChartウィンドウを生成し履歴グラフが表示されます。

 (c)DemPreChartウィンドウを終了すると、DemAppウィンドウが表示されて

 

前々回の流れで進みます。

 

(3)ユーザーの目から見ると、起動時(初回は「ログファイルを読み込めない」というエラーが出ますが、次回から)検査履歴の棒グラフを表示するので、それを確認の上、棒グラフのウィンドウを終了させると、出題ダイアログが出て、8文字入力すると正解不正解判定ダイアログが出て、次の出題に移ります。どの段階でもシステムボタン「X」または'Q'ボタンでプログラムを中止することができ、既に完了した検査結果はログに追加されます。

 

(4)Windows 11が動くPCなら、どなたでもこのプログラムを愉しむことができます。先ず、末尾のプログラムコードをテキストエディターに張り付け、"Dementia_Pre.cs"というファイル名で保存します。次にBCCForn and BCCSkeltonのサンプル、MSCompAssを使うか、または)次(↓)の"Dementia_Pre.bat"というバッチファイルを"Dementia_Pre.cs"と同じフォールダーに入れて、ダブルクリックすることでコンパイルができます。尚、以下のバッチファイルではプログラムアイコンやウィンドウアイコンがデフォルトのままですので、何か気に入ったアイコンBCCForn and BCCSkeltonのツール、EZImageとIconViewerで簡単に作れますよ)を"Icon.ico"というファイル名で同じフォールダーに入れておき、Dementia_Pre.batのピンク色の行の"/t:winexe"の後に、" (半角スペース)/win32icon:".\Icon.ico" (半角スペース)"をいれてコンパイルするとプログラムアイコン、ウィンドウアイコンが設定されます。

 

【Dementia_Pre.bat】→これをテキストエディターに貼って"Dementia_Pre.bat"で保存してください。

@ECHO OFF
ECHO ----------------------------------
ECHO  Dementia_Pre.bat
ECHO  Copyright (c) 2026 by Ysama
ECHO ----------------------------------

C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /t:winexe  /reference:"C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Windows.Forms.DataVisualization\v4.0_4.0.0.0__31bf3856ad364e35\System.Windows.Forms.DataVisualization.dll" /warn:4 ".\Dementia_Pre.cs"
pause
 

注意!:Webのテキストをエディターに張り付けるとUTF-8型式になると思いますが、csc.exeはShift-JIS、UTF-8何れもコンパイルできます。但しエディターによっては、または読み込み時にUTF-8をShift-JISとして読み込み、変換できず'?'が多発する場合もありますのでご注意ください。尚、BCCForn and BCCSkeltonの"SampleBCCSkelton\MSCompass\Debug\Sample\Dementia_Pre"フォールダーにあるソースファイルはShift-JISとなっています。

 

では、Enjoy!

 

【Dementia_Pre.cs】

////////////////////
//Dementia_Pre.cs
////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;        //Assemblyを使う為
using System.Diagnostics;        //Stopwatchを使う為
using System.IO;                 //StreamReader/Writerを使用するのに必要
using System.Text;                 //Encoding.GetEncodingを使用するのに必要
using System.Windows.Forms.DataVisualization.Charting;    //MS Chartを使用するのに必要

namespace Dementia_Pre
{
    //アプリケーションクラス(解説:DemAppを動かすだけのクラス)
    public class Launcher
    {
        [STAThread]
        public static void Main()
        {
            DemApp da = new DemApp();
            Application.Run(da);
        }
    }

    /////////////////////////////
    //Demntia_Preクラス(DemApp)
    /////////////////////////////

    public class DemApp : Form
    {
        
        //クラスフィールド
        Button[] Btns = new Button[36];                    //36個のボタン(解説:表示する数字は変わります)
        string[] txtStr =  {"0", "1", "2", "3", "4",    //ボタン表示用文字列(解説:乱数でシャッフルします)
                            "5", "6", "7", "8", "9",
                            "A", "B", "C", "D", "E",
                            "F", "G", "H", "I", "J",
                            "K", "L", "M", "N", "O",
                            "P", "Q", "R", "S", "T", 
                            "U", "V", "W", "X", "Y",
                            "Z"};
        string question, answer;                        //問題と答え(解説:文字列です)
        Stopwatch stw = new Stopwatch();                //ストップウオッチ(解説:所要時間を図ります)
        string Dem_Pre_Data;                            //Dementia_Preの履歴データ(解説:CSVファイルなので、Excelなどで読めますよ)

        //コンストラクター
        public DemApp()
        {
            //プログラムアイコンをフォームにつける
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);
            this.Size = new Size(640, 480);
            this.MinimumSize = new Size(320, 190);
            this.Text = "Dementia Prevention";
            this.Load += Initialize;
        }

        //初期化処理
        private void Initialize(object sender, EventArgs e)
        {
            //ボタン x 36
            int w = ClientSize.Width / 6;
            int h = ClientSize.Height / 6;
            for(int y = 0; y < 6; y++)    //解説:縦
            {
                for(int x = 0; x < 6; x++)    //解説:
                {
                    int i = y * 6 + x;
                    Btns[i] = new Button();
                    Btns[i].Size = new Size(w, h);
                    Btns[i].Location = new Point(x * w, y * h);
                    Btns[i].Text = txtStr[i];
                    Btns[i].Click += Buttons_Click;
                    this.Controls.Add(Btns[i]);
                }
            }
            if(LogRead())    //履歴ログを読む(解説:読めたらTrue、不可ならFalse)
            {
                //過去履歴データをグラフ表示
                DemPreChart dpc = new DemPreChart();
                dpc.ShowDialog();
                dpc.Dispose();
            }
            //新しいゲームの開始
            MakeNewQuestion();
        }

        //サイズ変更処理
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            //ボタン配置更新
            int w = ClientSize.Width / 6;
            int h = ClientSize.Height / 6;
            for(int y = 0; y < 6; y++)    //解説:縦
            {
                for(int x = 0; x < 6; x++)    //解説:横
                {
                    int i = y * 6 + x;
                    if(Btns[i] != null)
                    {    //解説:サイズと位置を変更
                        Btns[i].Size = new Size(w, h);
                        Btns[i].Location = new Point(x * w, y * h);
                    }
                }
            }
        }

        //終了処理
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
                e.Cancel = true;
            else
                LogWrite();            //履歴ログを書く
        }

        //すべてのボタンが押された時の処理
        private void Buttons_Click(object sender, EventArgs e)
        {
            //終了処理("Q"ボタンが押された時に確認してくる)
            if(((Button)sender).Text == "Q")
            {
                Close();
            }
            //答えを生成する
            answer += ((Button)sender).Text;
            //クリックされたボタンのNameを表示する
            MessageBox.Show(answer, "現在の答え", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //完了確認(解説:8文字入力されるまではスキップします)
            if(answer.Length == 8)
            {
                stw.Stop();    //解説:ストップウオッチを止めます
                TimeSpan ts = stw.Elapsed;    //分ts.Minutes, 秒ts.Seconds
                string erapsed_time = "所要時間: " + ts.Minutes + "分" + ts.Seconds + "秒";
                string msg, title;
                if(answer == question)
                {
                    msg = "正解しました!";
                    title = "正解";
                }
                else
                {
                    msg = "不正解でした!";
                    title = "不正解";
                }
                msg += System.Environment.NewLine + "question: " + question + System.Environment.NewLine + "answer  : " + answer + System.Environment.NewLine + erapsed_time;
                MessageBox.Show(msg, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
                Dem_Pre_Data += question + "," + answer + "," + (answer == question).ToString() + "," + ts.TotalMinutes.ToString() + System.Environment.NewLine;
                question = answer = string.Empty;
                MakeNewQuestion();
            }
            //新しいボタン表示文字の設定
            ShuffletxtStr();
        }

        //txtStr配列をシャッフルし、ボタン表示文字列も変更する
        private void ShuffletxtStr()
        {
            //乱数の初期化
            Random rand = new Random((int) DateTime.Now.Ticks & 0x0000FFFF);
            for(int i = 35; i >= 0; i--)
            {    //解説:36のアルファベット大文字、数字の配列をシャッフルしています
                int n = rand.Next(i);
                string temp = txtStr[i];
                Btns[i].Text = txtStr[i] = txtStr[n];
                txtStr[n] = temp;
            }
        }

        //新しいquesionを作成する
        private void MakeNewQuestion()
        {
            //乱数の初期化
            Random rand = new Random((int) DateTime.Now.Ticks & 0x0000FFFF);
            for(int i = 0; i < 8; i++)    //解説:乱数で8文字の出題を作成します
            {
                question += txtStr[rand.Next(36)];
            }
            MessageBox.Show("以下の文字列をよく覚えてOKボタンを押してください。" + System.Environment.NewLine + question,
                            "ゲーム開始", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //ストップウォッチを初期化して開始する
            stw.Reset();
            stw.Start();
        }

        ///////////////////
        //ログファイル処理
        ///////////////////

        private bool LogRead()
        {
            try    //解説:エラー処理の為
            {
                using (StreamReader file = new StreamReader(".\\Dem_Pre.log", Encoding.UTF8, false))
                {
                    //ファイルを読む
                    Dem_Pre_Data = file.ReadToEnd();
                    // ファイルを閉じる
                    file.Close();
                    return true;
                }
            }
            catch(FileNotFoundException e)    //FileNotFoundException用
            {
                MessageBox.Show("Dem_Pre.logファイルを読み込みで、エラーが発生しましたが続行します。\r\n" + e.Message , "Logファイル読込エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (Exception e)                //FileNotFoundException以外用
            {
                MessageBox.Show("エラーが発生しましたが続行します。\r\n" + e.Message , "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
        }

        private void LogWrite()
        {
            //累計を今回のデータで更新する
            using (StreamWriter file = new StreamWriter(".\\Dem_Pre.log", false, Encoding.UTF8))
            {
                //ファイルを書く
                file.Write(Dem_Pre_Data);
                // ファイルを閉じる
                file.Close();
            }
        }
    }

    ////////////////////
    //DemPreChartクラス
    ////////////////////

    public class DemPreChart : Form
    {
        //クラスコントロール
        Chart chart;        //MS Chartコントロール
        Button extBtn;        //終了ボタン
        //クラスフィールド
        double[] dat;    //解説:履歴の所要時間を記録するdouble配列です
        //コンストラクター
        public DemPreChart()
        {
            //プログラムアイコンをフォームにつける
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);
            //フォームの設定
            this.Size = new Size(640, 480);
            this.MinimumSize = new Size(320, 190);
            this.Text = "Dementia Prevention Log Chart";
            this.Load += Initialize;
        }

        //初期設定
        private void Initialize(object sender, EventArgs e)
        {
            //MS Chart
            chart = new Chart();
            chart.Location = new Point(10, 10);
            chart.Size = new Size(ClientSize.Width - 20, ClientSize.Height - 60);
            chart.Anchor = (AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom | AnchorStyles.Left);
//            chart.Legends.Add("Legend1");    //Addしないと「凡例」が表示されない
            this.Controls.Add(chart);

            //終了ボタン
            extBtn = new Button();
            extBtn.Location = new Point((ClientSize.Width - extBtn.Width) /2, ClientSize.Height - extBtn.Height - 10);
            extBtn.Text = "終了";
            extBtn.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
            extBtn.Click += exitButton_Click;
            this.Controls.Add(extBtn);

            if(IniChart())        //データを読み込む
                ShowChart();    //MS Chartを表示する
        }

        //終了ボタンが押された時の処理
        private void exitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        //Dem_Pre.logファイル読み込み
        private bool IniChart()
        {
            string data = "";
            //ファイル(UTF-8:Encoding.UTF8)を読む
            try
            {
                using(StreamReader file = new StreamReader("Dem_Pre.log", Encoding.UTF8))
                {
                    data = file.ReadToEnd();
                    // ファイルを閉じる
                    file.Close();
                }
            }
            catch(FileNotFoundException e)    //FileNotFoundException用
            {
                MessageBox.Show("Dem_Pre.logファイルを読み込めなかったので、履歴グラフを表示できません。このウィンドウを終了させてください。\r\nエラーコード:" + e.Message, "Logファイル読み込みエラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch(Exception e)    //FileNotFoundException以外用
            {
                MessageBox.Show("このウィンドウを終了させてください。\r\nエラーコード:" + e.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            //文字列の行分割
            string[] lines = data.Split(new[]{"\r\n","\n","\r"}, StringSplitOptions.None);
            //行の語分割と実数配列の作成
            dat = new double[lines.Length - 1];                //最終行は空行の為
            for(int ln = 0; ln < lines.Length - 1; ln++)    //最終行は空行の為
            {
                string[] dt = lines[ln].Split(',');
                bool flag;    //検査結果
                double sec;    //所要時間(秒)
                if(!bool.TryParse(dt[2], out flag))
                    return false;
                if(!double.TryParse(dt[3], out sec))
                    return false;
                dat[ln] = flag ? sec : -sec;    //解説:正解は正(青)、不正解は負(赤)
            }
            return true;
        }

        //MS Chartを表示する
        private void ShowChart()
        {
            //MS Chartの初期化
            chart.Series.Clear();
            chart.ChartAreas.Clear();
            chart.Titles.Clear();
            //タイトル設定
            Title title = new Title("過去の検査履歴(「不正解」はマイナス表示)");
            Title title1 = new Title("所要時間(分)");
            //チャートエリア作成
            ChartArea chartAria = new ChartArea("Area1");
            chartAria.AxisX.Title = "回数";
            chartAria.AxisY.Title = "分";
            //系列の作成
            Series series = new Series();
            series.Color = Color.Transparent;
            series.ChartType = SeriesChartType.Column;    //系列の種類を棒グラフに設定
//            series.LegendText = "凡例:所要時間(分)";    //系列の凡例を設置
            series.BorderWidth = 2;                        //系列の境界線太さ
            series.MarkerStyle = MarkerStyle.None;        //系列のポイントマークの形状
             //読み込んだデータでデータポイントを作成
            for (int i = 0; i < dat.Length; i++)
            {
                DataPoint dp = new DataPoint(i + 1, dat[i]);
                //値によって色を変更(解説:正解は正(青)、不正解は負(赤))
                if(dat[i] >= 0)
                    dp.Color = Color.Blue;
                else
                    dp.Color = Color.Red;
                series.Points.Add(dp);
            }
            //チャート設定
            chart.Titles.Add(title);
            chart.Titles.Add(title1);
            chart.ChartAreas.Add(chartAria);
            chart.Series.Add(series);
            chart.ChartAreas["Area1"].BackColor = Color.WhiteSmoke;    //背景色を変更
        }
    }
}