C# 初級講座
VB2019 Visual Studio 2022

第16回 直線で学ぶ配列

2022/8/7

この記事が対象とする製品・バージョン

Visual Studio 2022 Visual Studio 2022 対象です。
Visual Studio 2019 Visual Studio 2019 対象です。
Visual Studio 2017 Visual Studio 2017 対象外ですが、参考になります。
Visual Studio 2015 Visual Studio 2015 対象外ですが、参考になります。
Visual Studio 2013 Visual Studio 2013 対象外ですが、参考になります。
Visual Studio 2012 Visual Studio 2012 対象外ですが、参考になります。
Visual Studio 2010 Visual Studio 2010 対象外ですが、参考になります。
Visual Studio 2008 Visual Studio 2008 対象外ですが、参考になります。
Visual Studio 2005 Visual Studio 2005 対象外ですが、参考になります。
Visual Studio.NET 2003 Visual Studio.NET 2003 × 対象外です。
Visual Studio.NET 2002 Visual Studio.NET (2002) × 対象外です。
  Visual Studio Code 対象外ですが、参考になります。

 

目次

 

1.直線を題材に配列を学ぶ

今回は、複数の値をまとめて扱う「配列」(はいれつ)という機能を扱います。

題材として、Windows フォーム アプリを使って複数の直線を描画するプログラムを作成します。複数の直線を描画するには複数のPoint型の値を使用する必要があり、ここで配列が登場します。

 

今回作成するプログラムは地味です。完成すると次のような特に意味がない複数の直線が描画されます。

なお、今回の知識を活かして次回はマウスでなぞった軌跡を描画するプログラムを作る予定です。

 

2.コントロールの配置

まずは下記の設定で新規プロジェクトを作成してください。

プロジェクトテンプレート Windows フォーム アプリ
プロジェクト名 DrawPenLesson
ソリューション名 DrawPenLesson
フレームワーク .NET 6.0

 

フォームを少し大きめにサイズ変更して、その中にPictureBox(読み方:PictureBox=ピクチャーボックス)を1つ大きめに配置してください。

このような感じです。わかりにくいですが、フォームのほぼ全体に広げて配置してあるのがPictureBoxです。

PictureBoxはフォームに画像を表示したいときに便利に使用できるコントロールです。プロパティウィンドウでImageプロパティを設定して画像ファイルを指定するだけで画像を表示できます。今回は画像ファイルを表示したいわけではなく、自分で直線を描画したいのでImageプロパティは使用しません。その代わりにPaintイベントを使用します。

プロパティとイベントは次の通り設定してください。

コントロールの名前 種類 プロパティ/イベント 備考
イベント PictureBox1 PictureBox イベント Anchor Top,Bottom,Left,Right フォームの大きさの変更に対応します。
    イベント Paint   ハンドラーを生成してください。

 

Anchorプロパティ(読み方:Anchor=アンカー)は、親のサイズが変わった場合に位置とサイズをどう変更するかを制御します。これを使ってフォームが大きくなったり小さくなったりしたときに自動的にPictureBoxのサイズも大きくしたり小さくしたりできます。

今回PictureBoxは直接フォームに貼り付けるので、親はフォームです。実行時にユーザーはマウスなどでフォームのサイズを自由に変えられます。このとき、PictureBoxの上・下・左・右の四辺とフォームの上・下・左・右の四辺のそれぞれの距離を一定に保つかどうかをAnchorプロパティで指定できます。今回は四辺とも一定に保ちます。プロパティウィンドウでは十字型の子画面が表示されるので、上・下・左・右の太い線をクリックして選択状態にして、Enterを押してください。

値に Top, Bottom, Left, Right と表示されれば意図通り選択できています。

これで、フォームのサイズを変更してもPictureBoxは四辺とフォームの四辺の距離は変わりません。つまり、フォームを大きくするとPictureBoxも大きくなります。(そうしないと四辺の距離が一定ではななくなってしまうので)

 

3.直線を描く

3-1.ウォーミングアップ

配列の話をする前に直線を描く単純なプログラムをやってみてウォーミングアップします。

次の通りプログラムしてください。

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Draw(e.Graphics);
}

private void Draw(Graphics g)
{
    //背景色を黒にする。
    g.Clear(Color.Black);

    //ピンクの太さ5のペンを定義
    Pen boldPen = new Pen(Color.HotPink, 5);

    //直線を1つ描画
    Point p1 = new Point(20, 20);
    Point p2 = new Point(500, 200);
    g.DrawLine(boldPen, p1, p2); // p1 から p2 へ線を引く

    //直線をもう1つ描画
    Point p3 = new Point(100, 400);
    g.DrawLine(boldPen, p2, p3); // p2 から p3 へ線を引く

}

これで、プログラムを実行すると次のように表示されます。

なお、Anchorプロパティの効果で、フォームのサイズを変更するとPictureBoxのサイズも追従するので、ついでに少し試してみてください。

 

3-2.DrawLineメソッドで1本の線を描く

このプログラムではGraphicsクラスのDrawLineメソッド(読み方:DrawLine=ドロゥライン)を使って線を描いています。

DrawLineメソッドは直線を1本だけ描くメソッドです。数学的には「直線」ではなく「線分」の方が正しいと思いますが、この記事では「直線」と呼ぶことにします)。2回呼び出しているので、2本の直線が描画されます。

第1引数は使用するペン、第2引数が始点、第3引数が終点です。始点と終点はPoint型で指定します。

 

3-3.ペンの自作

使用するペンは Pens.Red や Pens.Blue のように、よく使うペンが定義されているPensクラスを利用して指定してもよいのですが、今回は少し太いペンで描きたかったので、自分で boldPen という変数で定義しました。

ペンは Penクラス なので、new を使ってコンストラクターを呼び出すことで自分で定義できます。コンストラクターにはいくつかありますが、ここでは第1引数にColor型の色、第2引数に太さを指定できるコンストラクターを使用しました。

 

 

4.複数の直線を描く

4-1.DrawLinesメソッド

ウォーミングアップができたところで、プログラムを改造して本題にうつります。

DrawLineメソッドを何度も呼び出して複数の直線を描くこともできるのですが、1回で複数の直線を描くDrawLinesというメソッドがあります(読み方:DrawLines=ドロゥラインズ)。名前はよく似ていますが最後に s がついている点で異なります。

DrawLinesメソッドは、1回の呼び出しで2本の直線を描くこともできますし、10本でも描けますし、100本でも描けます。

これを実現するために、複数の座標をまとめて引数に指定します。

この「複数の座標をまとめて」というところがポイントで、これを実現するために C# の「配列」という機能を使います。

配列はまさに複数の値をまとめる機能で、文字列型の複数の値を表現することもできますし、数値型の複数の値を表現することもできます。型は自由ですが、複数の型を混在させることはできません。今回はPoint型の複数の値を表現します。

 

4-2.プログラム

それでは、Drawメソッドの中身を次のプログラムに置き換えてください。

private void Draw(Graphics g)
{
    //背景色を黒にする。
    g.Clear(Color.Black);

    //▼赤い線
    //この線が通る座標を定義
    Point[] points1 = new Point[4];
    points1[0] = new Point(0, 0);
    points1[1] = new Point(150, 60);
    points1[2] = new Point(80, 200);
    points1[3] = new Point(500, 150);

    //赤い太さ5のペンを定義
    Pen pen1 = new Pen(Color.Red, 5);

    //線を描画
    g.DrawLines(pen1, points1);

    //▼青紫の線
    //この線が通る座標を定義
    Point[] points2 = new Point[6];
    points2[0] = new Point(300, 50);
    points2[1] = new Point(600, 500);
    points2[2] = new Point(50, 450);
    points2[3] = new Point(400, 110);
    points2[4] = new Point(500, 560);
    points2[5] = new Point(130, 40);

    //青紫の太さ8のペンを定義
    Pen pen2 = new Pen(Color.BlueViolet, 8);

    //線を描画
    g.DrawLines(pen2, points2);
}

このプログラムでは赤い3本の線と、青紫の5本の線を描きます。実行すると次のようになります。

DrawLinesメソッドは1回に1つのPenしか使えないので、赤いペンと青紫のペンの2つを使うために2回呼び出しています。

第2引数で座標の配列を指定します。DrawLinesメソッドは指定された配列の座標の間を順番に線でつなぎます。2個の座標を指定すると1本の直線となり、10個の座標を指定すると9本の直線となるという具合です。

 

4-3.配列

赤い線で座標の配列を作成する部分のプログラムは次のようになっています。

//▼赤い線
//この線が通る座標を定義
Point[] points1 = new Point[4];

points1 がPoint型の配列を表す変数です。変数を配列として宣言する場合は、型の名前の後に [] を付けて string[] や int[] のようにします。今回は Point型の配列なので Point[] としています。この [] が配列の目印です。

DrawLinesメソッドのインテリセンスでも第2引数の型は Point[] となっており、Point の配列を指定すべきことが読み取れます。

Point[] points1 = new Point[4] で、変数 points1 を Point型の4つの要素を格納できる配列として初期化しています。

new を使うことでコンストラクターを呼び出していますが、通常のクラスや構造体のコンストラクターの呼び出しと異なり ( ) は不要です。その代わり [ ] の中にこの配列で使用する要素の数を指定する必要があります。

 

その後、次のようにして配列に4つの座標を格納しています。

points1[0] = new Point(0, 0);
points1[1] = new Point(150, 60);
points1[2] = new Point(80, 200);
points1[3] = new Point(500, 150);

points1[0]が1つ目の要素を表し、points[1]が2つ目の要素を表すという具合です。[ ] の中に数字を入れて対象の要素を表すのが配列の構文です。この [ ] の中の数字を インデックス または「添え字」(そえじ)と呼びます。最初の要素にアクセスするには [0] を指定します。[1]は2番目です。0から始まるという点に注意してください。

 

このプログラムでは値の設定しか行っていませんが、同じようにして値を取得することもできます。

Point p = points1[1];
MessageBox.Show(p.ToString());

つまり、points1[0] や points1[1] という変わった書き方をしますが、value1 や value2 といった変数と同じように扱えるということです。

 

4-4.もうちょっと配列

配列については、今回説明した以外にもいろいろな機能があります。いずれ別の回で説明することになるものもあります。

配列は古い機能で、構造がシンプルで、高速に動作します。その代わりに、機能は扱いにくいです。

たとえば、次のように要素を3つもつ配列にそれぞれ値を追加してみます。

string[] names = new string[3];
names[0] = "Apple";
names[1] = "Banana";
names[2] = "Cat";

サイズ(=要素の数)を3つで宣言しているので、この配列で使用できるインデックスは 0, 1, 2 です。

さて、ここで4つ目の要素 "Dog" を追加するにはどうしたらよいでしょうか?

names[3]  = "Dog" などと書くと、IndexOutOfRangeException (読み方:IndexOutOfRangeException=インデックスアウトオブレンジエクセプション)が発生します。これはインデックスが範囲外であるときに発生する例外です。

C#の配列の登場から15年経った2017年に多少これを簡単に実行できるAppendメソッドが追加されました。次のようにして要素を追加できます。

string[] names = new string[3];
names[0] = "Apple";
names[1] = "Banana";
names[2] = "Cat";

names = names.Append("Dog").ToArray();

System.Diagnostics.Debug.WriteLine(names[3]); //Dog

これもちょっとわかりにくいのですけど、Appendメソッドで、namesの最後に"Dog"を追加します。追加した結果はもはや配列ではなくAppendPrepend1Iteratorというマイナーなクラスです。ToArrayメソッドでこれを配列に型変換します。それを変数namesに代入しなおします。結果として配列の最後に "Dog" が追加されたものが得られるというわけです。

 

このように配列は使いにくいので、代わりにもっと使いやすい似たような機能であるList(読み方:List=リスト)の方が好まれます。Listは次回説明しますが、たとえば、次のようにプログラムを記述できます。

List<string> names = new List<string>();
names.Add("Apple");
names.Add("Banana");
names.Add("Cat");
names.Add("Dog");

System.Diagnostics.Debug.WriteLine(names[3]); //Dog

Listは配列と違って宣言時にサイズ(=要素の数)を決めません。好きなだけ Addメソッド(読み方:Add=アド)を呼び出して要素を追加できます。個々の要素にアクセスするときは配列と同じようにインデックスを使います。

というわけで、配列を覚えてもあまり実用的ではないのですが、今回でてきたDrawLinesメソッドのように引数に配列を要求される場合もありますから、知っておく必要はあります。それから、配列の考え方はListなどの基本にもなっているので、実用的ではなくても知っておくべきです。

自主的に配列を使うのは要素の数が固定で、かつよほどシビアな性能を求めるときくらいなものです。たとえば、マルチメディアの生データを扱う場合などは配列の出番が多くなると思います。

 

5.練習問題

問1.親コントロールのサイズ変更に追従してサイズが自動的に変わるようにするにはどのプロパティを使いますか?
AutoSize
Anchor
Size
問2.次の配列 values では要素の数はいくつでしょうか?
int[] values = new int[3];
2
3
4
問3.次の配列 values ではインデックスの最大値はいくつでしょうか?
int[] values = new int[3];
2
3
4
問4.次のプログラムを実行するとどうなるでしょうか?
int[] values = new int[2];
values[0] = 123;
values[1] = 456;
values[2] = 789;
System.Diagnostics.Debug.WriteLine(values[2]);
456と表示される
789と表示される
例外が発生する

 

次の回では、マウスでなぞって線を書くプログラムを作ります。

C#初級講座講座 次の回へ