2018年5月11日金曜日

ひとまず完結のご挨拶

ご挨拶


2018年2月から始めたこのブログには、2018年1月中旬から約2週間、集中的に取り組んだプログラミング言語C#の学習の足跡を35回に渡って記してきました。

本来なら、こういった内容のものは、Qiitaあたりに書いて、現役プログラマに揉まれるべきなのかもしれませんが、そこまで臨戦体制的なものではなく、自分が覚えておく為の記録の意味合いで書いて来ました。

そもそも、気が弱いのであそこに何か書くのは無理です(笑)

C#学習のそもそもの発端は、最初のご挨拶にも記したとおり、お仕事としてC#でプログラム開発が出来ないか、という打診を頂いたことでした。

残念ながらお仕事にはなりませんでしたが、2週間という短い期間で、Windows環境で動くGUIアプリケーションを書くことが出来るようになったのは大きな成果と自分では思っています。

今回、当初の目標仕様を満足するプログラムの作成の完了まで来ましたので、記事の更新はこれで一段落ということにします。しかし、今後も色々なソフトウエア開発のお勉強や研究をすることはあると思いますので、その際にはこのブログに足跡を残していきたいと考えています。

お付き合いいただき、ありがとうございました。そして、今後ともよろしくお願いいたします。

筆者:冨樫伸也

今までの成果を全部入れて、いよいよプログラム完成です

前回の記事で、Form(Form1)上のPictureBox(pictureBox1)にマウスの動きに合わせて動く図形(丸)を描くことが出来ました。

ここまで来れば、PictureBoxの中に縦又は横の直線を描いて、マウスの動きに合わせて動かすのは難しいことではありません。出口はもうすぐです。

2018年5月10日木曜日

マウスに合わせて図形を動かす(2) 〜 使いやすさを求めて

前回の記事で、マウスの動きに合わせて黒丸を動かすことが出来るようになりました。しかしながら、これは単純なFormの上での話です。

次は、これを輪切り描画プログラムに移植します。

前回の記事では、Paintイベントのイベントハンドラとして自前のメソッドを準備して、その中に描画処理を記述し、メソッド黒丸を描く座標を指定した後に、Invalidateメソッドを呼び出してPaintイベントを発生させ、描画するという流れの処理を書きました。

最初は、これを単純に今まで作ってきた輪切り画像描画プログラムに持っていくことを考えました。

当初からあるMouseMoveイベントのイベントハンドラの中に、黒丸を描く座標を計算し、再描画のためのInvalidate()メソッドを呼び出すコードを入れます。

なお、リスト中のマウス座標を取得するメソッドgetMousePosition()は、以前のCursor.Positionプロパティを読んで座標変換する処理方法から、MouseMoveイベントの引数として与えられるウインドウ内座標系のマウス座標を、描画データの座標系に変換する処理に変更しました。

        
  リスト1 MouseMoveイベントのイベントハンドラ
        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            // イベントが発生したときのマウス座標を取得
            Point point = new Point( e.X, e.Y ); 
            // この処理で、ウィンドウ座標で入手したマウス座標を、データ座標系に変換
            getMousePosition( point );           
            //黒丸を描く座標を算出
            this.pt = new Point(e.X - (size.Width / 2), e.Y - (size.Height / 2));
           // 描画処理で使う座標をグローバル変数に設定、
           // 新たにインスタンスを作る必要がないと思うので、pointをそのまま代入
            this.pt2 = point;     

            this.Invalidate();       // 再描画
        }

さらに、前回のプログラムと同様にOnPaint()メソッドをオーバーライドして丸を描くコードを入れます。なお、pictureBox1は背景が黒なので、丸の色を白に変更しています。

リスト2 描画メソッド
        protected  override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Brush brush = new SolidBrush(Color.White);

            e.Graphics.FillEllipse(brush, pt.X, pt.Y, size.Width, size.Height);
        }

ところが、この方法はうまくいきません。白い丸がForm1の上で動いているのを黒いpictureBoxが隠してしまっているように見えます。試しにthis.Invalidate();を、this.pictureBox1.Invalidate();に変更すると、丸は動かなくなります。どういうことでしょうか?

ここからは私の推理ですが、リスト1におけるthis.Invalidate();のthisはpictureBox1ではなくて、Form1を指しています。また、リスト2におけるOnPaint()の基底クラスはForm1.OnPaint()になるんだと思います。

つまり、再描画要求はあくまでもForm1に対する再描画要求であり、描画処理もForm1に対するOnPaint()メソッドのオーバーライドになります。つまりpictureBox1は蚊帳の外でなのです。

this.Invalidate();をthis.pictureBox1.Invalidate();に変更した場合は、再描画要求は出るものの、pictureBox1には何も描画されていない状況であるのに加えて、再描画処理は何も手を加えていないpictureBox1の再描画処理が走るだけ。

そしてForm1のOnPaintは起動しないので、Form1内に描画をするコードが動かず、何も起こらない、丸は動かない、ということになるのだと思います。

pictureBox1にも再描画の為のOnPaintメソッドはあるのですが、これはPictureBoxクラスの中のメソッドなので、Form1からオーバーライドして使ったりすることはできません。

色々調べ回って試行錯誤した結果、PaintイベントのイベントハンドラをOnPaintメソッドのオーバーライドとして記述するのではなく、Visual StudioのpictureBox1のプロパティから、明示的にイベントハンドラを定義すればよいのではないか、ということになりました。

これに加えて、再描画要求をthis.pictureBox1.Invalidate();と記述し、要求先をpictureBox1に、明示的に指示することにしました。

これによって、動作の流れは、MouseMoveイベント→描く丸の座標を計算→pictureBox1再描画要求→pictureBox1のPaintイベント発生→pictureBox1への描画処理、という流れに明確化します。

描画処理のコードは以下のようにしました。

リスト3 再描画メソッド
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            Brush brush = new SolidBrush(Color.White);

            //ここに図形を描く処理を追加すると、マウスの動きに合わせてその図形が動いてくれるようになる。
            e.Graphics.FillEllipse(brush, pt.X, pt.Y, size.Width, size.Height);
        }

このような動作になります。
今日はここまでにします。

2018年5月9日水曜日

マウスに合わせて図形を動かす 〜 使いやすさを求めて

前回、輪切り画像作成に於いて、どこで切るのかを指示するときに、切る場所をわかりやすく見せるために、マウスカーソルの形状を変えてみました。

ここからさらに進化させて、マウスカーソルの動きに合わせてpictureBoxの中を縦に又は横に貫く直線を動かし、輪切りする場所をよりわかりやすく表示、指定できるようにしたいと思います。そのための準備として、マウスに合わせて図形を動かす方法を研究します。

マウスの動きに合わせて連続的に物体を動かす


世の中、インターネットを検索すると、マウスの動きに合わせて何かを動かす、というプログラムの例は沢山出て来ます。需要が多いのでしょう。

以下は、Formを表示させ、その中で黒丸をマウスの動きに合わせて動かす、というプログラムです。色々なWebで勉強させていただき、Wisdom Soft様で学ばせていただいたプログラムです。


リスト1 マウスの動きに合わせて黒丸がうごく
(名前空間参照の為のusingは省略)

namespace mousetest
{
    public partial class Form1 : Form
    {
        private Point pt;  // 黒丸の座標を保持
        private Size size = new Size(40, 40);  // 黒丸のサイズを保持

        private void Test_MouseMove( object sender, MouseEventArgs e )
        {
            // Point構造体に、これから黒丸を描く座標を設定
            this.pt = new Point(e.X - (size.Width / 2), e.Y - (size.Height / 2));
            // Form1のコントロール領域を無効にして、描画メッセージを送る
            this.Invalidate();
        }

       // OnPaintメソッドをオーバーライド
        protected override void OnPaint(PaintEventArgs e) 
        {
            base.OnPaint(e);  // 継承元クラスの再描画処理を実施
            Brush brush = new SolidBrush(ForeColor);  // これから黒丸を描くのに使うブラシを設定
            e.Graphics.FillEllipse(brush, pt.X, pt.Y, size.Width, size.Height);  // 黒丸を描画
        }

        
        public Form1()
        {
            InitializeComponent();
            // MouseMoveイベントのイベントハンドラとしてTest_MouseMoveを登録
            this.MouseMove += new MouseEventHandler(Test_MouseMove);
        }
    }
}

上記リストにおいて、MouseMoveイベントのイベントハンドラとしてTest_MouseMoveメソッドを記述しています。

この中では、これから描く黒丸の中心の座標を、MouseMoveイベントへの引数の中にあるマウスマーソルの座標と黒丸の大きさから計算し、Point構造体ptに設定した後、再描画要求の為にthis.Invalidate()メソッドを呼び出しています。これによってOnPaintイベントが発生します。

一方、通常のOnPaint()メソッドをオーバーライドして定義している、このプログラムのOnPaint()メソッドでは、通常のOnPaintイベントで実行されるはずの再描画処理をbase.OnPaint(e);で実行した後、黒丸を描くための描画ブラシの設定と描画処理を実施します。

base.OnPaint(e);の呼び出しで、黒丸を描く前のウインドウの中の絵柄が描画されるので、移動前の黒丸は消えて、移動後の黒丸が描かれる、という仕組みです。

この方式を活用して、PictureBoxの中でマウスの動きに合わせて直線を動かす処理を次回入れたいと思います。

今日はここまでにします。


2018年5月8日火曜日

マウスカーソルを変える 〜 使いやすさを求めて

前回で、輪切り画像の描画処理は一通り開発を終わりました。

ただ、輪切りをする場所を指定する画面の使いやすさを、もう少し向上したいと思ったんです。要するに、どこで切るのかを操作している人がわかりやすい仕掛けを入れたいと思った、ということです。

一番簡単な方法は、必要なときにマウスカーソルの形状を縦棒もしくは横棒に変えると言うものだと思います。マウスカーソルのライブラリーにはVSplit、HSplitという名前のカーソルがあるので、これを使います。

プログラムの中に、輪切り画像作成モードを設定する処理、checkBox1_CheckedChanged()、checkBox2_CheckedChanged()という2つのメソッドがあります。輪切り画像作成モードに入る為のボタンを押したときに呼び出されるイベントハンドラです。この中で、マウスカーソルの切替をします。


         /* X軸に垂直な平面で輪切りにする場合の処理 */
        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBox2.Checked && !event_by_inner1)
            {
                event_by_inner1 = true;
                checkBox2.Checked = false;
            }

            event_by_inner1 = false;

            if (checkBox1.Checked)
            {
                checkBox1.Text = "Push Button to stop getting mouse position X-axis";
                button1_on = true;
                mouse_x = mouse_y = 0; // マウス座標データ初期化
                // マウスカーソルを、縦向きに分割するようなものに変更
                this.pictureBox1.Cursor = System.Windows.Forms.Cursors.VSplit;
                // マウスイベント有効
                ctrlEvent(true);
            }
            else
            {
                checkBox1.Text = "Push Button to start getting mouse position X-axis";
                button1_on = false;
                // マウスカーソルを元に戻す
                this.pictureBox1.Cursor = System.Windows.Forms.Cursors.Default;
                // マウスイベント削除
                ctrlEvent(false);
            }
        }

X軸に垂直な平面で輪切りにするモードに遷移したら、pictureBox1.CursorプロパティをSystem.Windows.Forms.Cursors.VSplitに書き換えて縦向きの直線に変更し、輪切りモードから通常モードに遷移したらSystem.Windows.Forms.Cursors.Defaultに書き換えてデフォルトのマウスカーソルに戻します。

         /* Y軸に垂直な平面で輪切りにする場合の処理 */
        private void checkBox2_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBox1.Checked && !event_by_inner1)
            {
                event_by_inner1 = true;
                checkBox1.Checked = false;
            }

            event_by_inner1 = false;

            if (checkBox2.Checked)
            {
                checkBox2.Text = "Push Button to stop getting mouse position Y-axis";
                button2_on = true;
                mouse_x = mouse_y = 0; // マウス座標データ初期化
                // マウスイベント有効
                // マウスカーソルを水平の向きに分割するものに変更
                this.pictureBox1.Cursor = System.Windows.Forms.Cursors.HSplit; 
                ctrlEvent(true);
            }
            else
            {
                checkBox2.Text = "Push Button to start getting mouse position Y-axis";
                button2_on = false;
                // マウスカーソルを元に戻す
                this.pictureBox1.Cursor = System.Windows.Forms.Cursors.Default; 
                // マウスイベント削除
                ctrlEvent(false);
            }
        }
Y軸に垂直な平面で輪切りにするモードに遷移したら、pictureBox1.CursorプロパティをSystem.Windows.Forms.Cursors.HSplitに書き換えて横向きの直線に変更し、輪切りモードから通常モードに遷移したらSystem.Windows.Forms.Cursors.Defaultに書き換えてデフォルトのマウスカーソルに戻します。

動作の様子をアニメーションgifにしてみました。下に貼り付けます。


今回はここまでです。

2018年5月7日月曜日

輪切り画像の描画処理はこれで完成

拡張メソッド、ラムダ式のエントリーを挟んだ前回のエントリーで、輪切り画像の描画のための基本的な部品は出来上がりました。

前回までの進捗を整理すると、


  • データファイルをOpen File Dialogを使って選択
  • Visual BasicのCSVパーサーを使ってCSVのデータをArrayListに格納
  • プロット
  • 輪切りする場所を指定する為のUIを実装(X座標を指定するかY座標を指定するかを排他的に選択
  • マウスでクリックした場所の座標を拾う(X座標のみ)
  • 拾った座標に最も近いデータをArrayListから探してくる(X座標のみ)


というところまで、実装済みです。この後は、


  • 奥から描画していくようにデータを並び替え(隠面処理)
  • マウスでクリックした場所の座標を拾う(前回はX軸だけだったのでY軸も含めて)
  • 拾った座標に最も近い座標をArrayListから探してくる(X座標、Y座標ともに)
  • ArrayListから見つけてきた一番近い座標を含む全てのデータを取得
  • プロット

という機能を実装すれば、輪切り画像の描画処理は完成となります。

奥から描画していくようにデータを並び替え


立体的な物体を画面に描くことは、物体の形状を平面に投影するということになります。その際に気をつけなければいけないのが、見える場所は見えるように、隠れて見えない場所は見えないように描くこと。

人間が鉛筆で絵を描く場合は、隠れて見えない場所は描かないのでしょうが、コンピューターに描かせる場合は、隠れて見えない場所は描かない、という処理をしてあげる必要があります。これを隠面処理と言います。

隠面処理には色々な方法があるようですが、今回のプログラムでは、見ている場所から遠い点から描画して、後ろを遮るものが前にある場合は、後ろの点を重ね書きして隠してしまう方法を採用します。一番単純な方法だからです。

プログラムが読むデータファイルは、データの順番が都合の良いように並んでいるとは限らない、と仮定すると、描画する前にデータを画面奥から手前に向かう座標軸、今回の場合はZ軸に関して並べ替える必要があります。

並べ替えの手法は、「業務開発で使えるプログラム」様にて勉強させていただきました。

描画する点のデータは、X座標、Y座標、Z座標及び点における測定値の大きさ、という4つのデータがひとかたまりになっています。これらをExcelのソートのように、1つのデータ、Excelのワークシートに例えるなら、1つのカラムをキーにして並べ替えたいわけです。

これは、バラバラのArrayListのままではなかなか難しいので、データテーブルというデータ形式を使って表形式のデータにまとめてから取り扱うのがよさそうです。

            //
            // 後でデータの並べ替えが必要になるので、プロットデータをデータテーブルに格納
            //
            table.Columns.Add("x", Type.GetType("System.Int32"));
            table.Columns.Add("y", Type.GetType("System.Int32"));
            table.Columns.Add("z", Type.GetType("System.Int32"));
            table.Columns.Add("dt", Type.GetType("System.Int32"));

            for (int i = 0; i < x_axis_dt.Count; ++i)
            {
                //
                // 実座標をウィンドウ座標に変換するためにオフセット加算
                //
                int x = (int)x_axis_dt[i] + Constants.offset_x;
                int y = -1 * ((int)y_axis_dt[i]) + Constants.offset_y;
                int z = (int)z_axis_dt[i] + Constants.offset_z;
                int d = (int)dt_dt[i];

                x_axis.Add(x);
                y_axis.Add(y);
                z_axis.Add(z);
                dt.Add(d);

                table.Rows.Add(x, y, z, d);  //データ1行分追加
            }
            
            // データテーブルのソートをするために、Dataviewを使う
            DataView dv = new DataView(table);
            dv.Sort = "z";        // 昇順 -> Z座標の数値が大きい方が手前。小さい方が奥
            //dv.Sort = "z DESC"; // 降順 -> Z座業の数値が小さい方が手前、大きい方が奥
            table = dv.ToTable();

データテーブルを作った後は、DataViewというクラスを使って、簡単にデータテーブルの並べ替えをすることができます。DataViewにデータテーブルを設定して、並べ替えをして、並べ替えたデータを元のデータテーブルに書き戻すまで、DataView dv = new DataView(table);の行からたった3行で記述することができます。

マウスでクリックした場所の座標を拾う


前回、X軸に垂直な平面に関して輪切りを行う場合について、クリックした場所の座標を拾う処理を実装しました。今回は、Y軸に垂直な平面に関して輪切りを行う場合の処理も合わせて組み込みます。

作成したプログラムの中では、Form1の上にあるpictureboxに対する描画処理が終了したあと、ユーザーが輪切り画像描画のボタンを押すことにより輪切り画像描画モードに遷移します。

このモードに遷移すると、Clickイベント、MouseMoveイベント、MouseHoverイベントが有効となり、マウスカーソルがpictureboxの上を動くたびにマウスカーソルの座標が、クラス内のグローバル変数であるmouse_x、mouse_yの中に記録され、更新され続けます。

このmouse_x、mouse_yのデータを拾ってくることで、マウスでクリックした場所の座標を知ることが出来ます。

拾った座標に最も近い座標をArrayListから探してくる

輪切り画像の描画を行うメソッドであるdrawSlice()の冒頭で、button1が有効かbutton2が有効かを調べて、X軸に関して輪切りをするか、Y軸に関して輪切りをするかを切り分け、それぞれでX軸の場合はmouse_x、Y軸の場合はmouse_yの値を取り出し、これに最も近い値をArrayList x_axis_dt_int(X軸に関して輪切りにする場合) y_axis_dt_int(Y軸に関して輪切りにする場合)から見つけてきます。

下記のプログラムリストにおいて、

// マウスでポイントした座標に最も近いデータ上の座標を拾ってくる
nearest_point = x_axis_dt_int.Nearest(mouse_x);  
nearest_point = y_axis_dt_int.Nearest(mouse_y);

の部分です。上はX軸に関して、下はY軸に関して輪切りにする場合の座標取得です。
ただし、ここで気をつけたいのは、まだ描画処理が走っていない状態で、ArrayListから最も近い値を探してくる処理を走らせてはいけないことです。これを防ぐ為に、描画データを格納するArrayListにデータが入っているかどうかを、逆に言えばArrayListが空でないかどうかをテストする処理を入れています。これは拡張メソッドIsEmpty()として定義しています。

実はあえて拡張メソッドにする必要もないのですが、使うときの書きやすさで、空かどうかをbool値で返す拡張メソッドにしてみました。

ArrayListから見つけてきた一番近い座標を含む全てのデータを取得


拡張メソッドNearest()を使ってArrayListから探してきた数値(X座標もしくはY座標)を含む座標データを、座標データが格納されているArrayListから取得します。

座標データは、データファイルのCSVデータを読み込んだ後、座標変換を行い、一番奥にある座標から順に並ぶように並べ替えたものが、x_axis_dt_int、y_axis_dt_int、z_axis_dt_int、dt_dt_intに格納されています。

この座標データに対して、X軸に対して垂直な面で輪切りにするのであればx_axis_dt_intからx_axis_dt_int.Nearest()で探してきた数値と等しい座標データを全て、Y軸に対して垂直な面で輪切りにするのであればy_axis_dt_intからy_axis_dt_int.Nearest()で探してきた数値と等しい座標データを全て取得します。

プロット


記事ではデータの取得とプロットを別項目にしましたが、実際にはNearest()で探した数値と同じX座標、もしくはY座標を持ったデータかどうかをif分で振り分けて、目的の座標であれば、そのままプロットの色を決めてプロットしてしまうような処理にしました。

以下、上記説明をコーディングしたプログラムリストを以下に示します。pictureBox1_Click()は、pictureBox1の上でマウスをクリックしたときに発生するイベントのイベントハンドラー、drawSlice()は実際に輪切り画像を描画するメソッド、IsEmpty()はArrayListの中身が空かどうかをテストするための拡張メソッドとなります。ArrayListの中身が空だった場合は、何もしないで呼び出し元に戻ります。

なお、拡張メソッドIsEmpty()は、コガネブログ様で教えていただきました。ありがとうございます。

        /*
         * pictureboxの上でマウスをクリックしたときに発生するイベント
         * 何も描かれていない状態でスライス描画処理を呼び出すと、データが入っているリストに
         * 中身がある前提の処理に
         * 支障を来すし、何も描かれていない状態でスライス描画処理を呼び出すことは意味が
         * 無いことなので、何も描かれて
         * いないときにはスライス描画処理が行われないようにする必要がある。
         * その判断をここで実施。
         */
        private void pictureBox1_Click(object sender, EventArgs e)
        {
            // データを入れるリストが単独で空であることはあり得ないので、
            // X軸もしくはY軸の座標データがあるかどうか一つだけのテストで判断
           if (!x_axis.IsEmpty())
            {
                drawSlice();  // データのリストが空でなければスライスを描く処理を呼び出す
            }
            else
            {
                MessageBox.Show("何も描かれていません");
            }
        }
        /*
         * スライス画像を作成する
         */
         private void drawSlice()
        {
            int nearest_point;
            Color col = new Color();

            Form3 frm3 = new Form3();
            frm3.Show();

            Bitmap img = new Bitmap(Constants.size_slicebox_x, Constants.size_slicebox_y);

            if (button1_on)
            {

               // マウスでポイントした座標に最も近いデータ上の座標を拾ってくる
               nearest_point = x_axis_dt_int.Nearest(mouse_x);  
                frm3.Text = "Slice by X-axis : " + nearest_point;
               // ArrayListの全ての要素数でループを回し、一番近い数値に関してだけ描画処理を動かす
                for (int i = 0; i < x_axis_dt.Count; ++i) 
                {
                    if (x_axis_dt_int[i] == nearest_point) 
                    {
                        // プロットデータの内容から描画色を決定
                        if (dt[i] != 0)   // データが0の時は描画しない
                        {
                            switch (dt[i])
                            {
                                case 0:
                                    col = Color.Black;
                                    break;
                                case 1:
                                    col = Color.Blue;
                                    break;
                                case 2:
                                    col = Color.Red;
                                    break;
                                case 3:
                                    col = Color.Purple;
                                    break;
                                case 4:
                                    col = Color.Green;
                                    break;
                                case 5:
                                    col = Color.Yellow;
                                    break;
                                case 6:
                                    col = Color.Violet;
                                    break;
                                case 7:
                                    col = Color.White;
                                    break;
                                default:
                                    col = Color.Magenta;
                                    break;
                            }
                        }
                        img.SetPixel(z_axis[i], y_axis[i], col);
                    }
                }
                frm3.pictureBox1.Image = img;
            }

            //
            // Y軸でスライスする場合の処理
            //
            if(button2_on)
            {
                nearest_point = y_axis_dt_int.Nearest(mouse_y);
                frm3.Text = "Slice by Y-axis : " + nearest_point;

                for (int i = 0; i < y_axis_dt.Count; ++i)
                {
                    if (    y_axis_dt_int[i] == nearest_point)
                    {
                        // プロットデータの内容から描画色を決定
                        if (dt[i] != 0)   // データが0の時は描画しない
                        {
                            switch (dt[i])
                            {
                                case 0:
                                    col = Color.Black;
                                    break;
                                case 1:
                                    col = Color.Blue;
                                    break;
                                case 2:
                                    col = Color.Red;
                                    break;
                                case 3:
                                    col = Color.Purple;
                                    break;
                                case 4:
                                    col = Color.Green;
                                    break;
                                case 5:
                                    col = Color.Yellow;
                                    break;
                                case 6:
                                    col = Color.Violet;
                                    break;
                                case 7:
                                    col = Color.White;
                                    break;
                                default:
                                    col = Color.Magenta;
                                    break;
                            }
                        }
                        img.SetPixel(x_axis[i], z_axis[i], col);
                    }
                }
                frm3.pictureBox1.Image = img;
            }

/*
 * 配列やリストが空かどうかを返す拡張メソッドisEmpty.cs
 * http://baba-s.hatenablog.com/entry/2015/07/17/100000
 */
using System.Collections.Generic;

public static class IListExtentions
{
    public static bool IsEmpty<T>( this IList<T> self )
    {
        return self.Count == 0;
    }
}

下に、プログラムを動作させた様子をキャプチャした動画を示します。輪切り画像がきちんと表示できればよかったのですが、色々な都合で綺麗に出ていないことをお許し下さい。


ソースファイルはここにzipファイルにして置きます。ソースのみですので、Visual Studioでビルドして動かす場合には、色々な設定が必要になると思います。それに関しては、誠に勝手ながら、各自にてお願いいたします。また、プログラムソースは学習用の使用に留めて下さいますようお願いいたします。

本プログラムもWindows10上のVisual Studio 2017 Communityエディションで開発、動作確認しています。動作環境として.Net Frameworkを使用していますので、Mac OS版のVisual Studio 2017では動かすことはできません。GUIを全て書き換える必要があると思います。

なお、プログラムソースの内容については無保証です。ご質問は可能な範囲でお受けいたしますが、回答にお時間を頂く場合もございますことをご了承下さい。