C# 初級講座
VB2019 Visual Studio 2022

第13回 円を描く

2022/7/10

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

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.今回の狙いと作成するプログラム

1-1.狙い

今回から何回かに分けてグラフィックのプログラムを扱います。

注目してほしいのはグラフィック機能よりも、クラスや構造体のインスタンスをどうやって取得しているのか、メンバーをどうやって呼び出しているのかという部分です。インスタンスから呼び出しているのか、静的メンバーとして呼び出しているのか、引数はどうやって何を渡しているかなどです。

グラフィック機能については、プログラムの理解を深める実体験の題材にしているだけですので、完璧主義にならず、よくわからないようなことがあっても次へ次へと学習を進めていくのが良いと思います。

 

1-2.サンプルプログラム

クリックした位置に円を書くプログラムを作ってみましょう。

完成するプログラムは次のようなものです。

※動画内では円が欠けて見えますが、本当はちゃんと正円です。

 

あまり面白いプログラムではありませんが、はじめて画像描画するプログラムなので軽めの題材にしてみました。

 

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

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

 

2.マウスクリック時の処理

2-1.MouseClickイベント

フォームには何も配置する必要はありません。少し大きめにサイズだけ変更してください。

フォームのMouseClickイベントハンドラー(読み方:MouseClick=マウスクリック)に次の通りプログラムします。

 

/// <summary>クリックした位置</summary>
Point clickPosition = new Point(-1,-1);

private void Form1_MouseClick(object sender, MouseEventArgs e)
{
    //クリックした位置を記録します。
    clickPosition = e.Location;
    //画面を再描画が必要であることをフレームワークに伝えます。
    //→フレームワークはこれを受けて描画を実行するために Paint イベントを発生させます。
    this.Invalidate();
}

まず、イベントハンドラーの外側、上で Point 側の変数 clickPosition を定義します。Point側は座標上の「点」を表す構造体で、座標を表すXプロパティとYプロパティを持ちます。コンストラクターを使って初期値に座標(-1, -1)、つまりXプロパティに-1、Yプロパティに-1を設定しておきます。

MouseClickイベントは文字通り、マウスでクリックしたときに発生します。

このイベントハンドラーのMouseEventArgs型(読み方:MouseEventArgs=マウスイベントオーグス)の第2引数 e には、クリックされた座標や押されたマウスのボタンなど、イベントに関連する情報が渡されます。

このプログラムでは マウスがクリックされた座標を表すLocationプロパティ(読み方:Location=ロケーション)を使って、座標を変数clickPositionに保存します。LocationプロパティもPoint構造体です。

次の行ではフォームの Invalidateメソッド(読み方:Invalidate=インバリデイト)を呼び出します。このメソッドは、フォームの再描画が必要であることをフレームワークに通知します。この意味は、はじめての人にはとてもわかりにくいと思いますので補足します。

 

2-2.Windowsアプリの描画の仕組み

画面上に何かグラフィックを描画する場合、各アプリケーションは勝手に画面に描画することは通常Windowsによって禁止されています。どこに何を書くかはWindowsが決定します。

だって、考えてみてみてください。あなたのアプリケーションは他のメモ帳やフォルダーのウィンドウの下に半分だけ隠れてしまっているかもしれません。Excelの下に全体が隠されてしまっているかもしれません。それを無視して画面に描画したらウィンドウの前後関係がめちゃくちゃになってしまいますよね。

Windowsはこういうことを管理しているので、必要な時に必要なアプリケーションに画面を描画するようにメッセージを送ります。描画指示のメッセージは WM_PAINT という名前がついています。このWM_PAINTメッセージを受け取ってはじめてアプリケーションは描画を実行するわけです。

このあたりの細かい処理は全部フレームワークがやってくれるので、プログラムでは通常気にしません。WM_PAINTというメッセージの名前を目にすることすらないでしょう。Windows フォーム アプリの場合フレームワークはWindowsから描画指示のWM_PAINTメッセージを受け取ると、フォームのPaintイベント(読み方:Paint=ペイント)を発生させます。この中にプログラマーは描画処理を記述することができます。

このような仕組みなので、Windows フォーム アプリで何かグラフィックをプログラムしたい場合は原則として Paint イベントを利用することになります。

Invalidateメソッドは、Windowsに対して、画面描画が必要な何かが発生していることを通知し、自分自身にWM_PAINTメッセージを送信するように依頼します。結果としてPaintイベントが発生します。

少しまわりくどい手続きですが、複数のアプリが同時に稼働するのをOSが管理しているという構造上このような手順を踏むのが正です。

 

 

3.Drawメソッドを作ってみる

3-1.Paintイベント

次はフォームのPaintイベントハンドラーに次のようにプログラムしてください。前述したようにInvalidateメソッドが呼ばれると、このPaintイベントが発生します。

 

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

このプログラムをした時点では、Drawのとろこに赤い波線が表示されエラーになります。

この Draw は何かというと、フォームに赤い円を描画するメソッドなんですが、このメソッドは、これからみなさんがプログラムするのです。

初級講座はじめてメソッドを自分で作成することになるというわけです。

今まではメソッドと言えば、フレームワークに初めから存在するメソッドを呼び出すだけでしたが、今回は違います。自分でメソッドを作って、それを呼び出すのです。

この時点では、Drawメソッドはまだ存在せず、呼び出しだけを記述しているのでエラーになるというわけです。

 

3-2.メソッドの作成

メソッドの記述方法はいろいろあります。戻り値も引数もない単純なメソッドの記述例は次の通りです。

private void メソッド名() {

     //ここにメソッドの内容を記述します。

}

今回はメソッドの作り方を説明したいわけではありませんが、気になる方のために少し説明しておきます。気にならない方は流し読みしていただいて構いません。

フォームのプログラムの上の方に「public partial class Form1 : Form」と記述されているが確認できると思います。これは、みなさんがプログラムしているのは Form クラスではなく、 Form1 クラスであることを示しています。だから、この Form1 クラスの内容としてメソッドを記述するとそのメソッドは Form1 クラスのメンバーということになります。

private (読み方:private=プライベート)は、このメソッドは Form1 内部でのみ使用されることを示しています。まだ初級講座では説明していませんが、本格的なプログラムを作るとたくさんのクラスを自作することになります。 private で定義したメソッドは他のクラスからは呼び出すことができず、そのメソッド内部からのみ呼び出せるようになります。どこからでも呼び出せるメソッドが作りたい場合は、この位置に public と記述します。他にも protected や internal など呼び出せる場所をもう少し細かく制御することもできます。詳細の説明は別の機会を待ちましょう。

続く void (読み方:void=ボイド)はこのメソッドに戻り値がないことを示しています。戻り値がある場合はここに戻り値の型を記述します。たとえば、int や string です。

メソッド名の後の空の ( ) は引数がないことを示しています。引数がある場合は変数の宣言と同じように型名と引数名を記述します。たとえば、(int x) や (string name) です。引数が2つ、3つある場合はカンマで区切って記述します。(int x, int y) や (string name, int number, DateTime expire) などです。

 

3-3.Drawメソッド

それでは、Form1_Paintの最後の } の下に次の通りプログラムしてください。

これであなたの Form1 には 赤い円を記述するDrawメソッドが追加されます。

private void Draw(Graphics g)
{

    g.Clear(Color.Black); //背景を黒でクリアします。

    if (clickPosition.X == -1)
    {
        //どこもクリックされていない場合(=プログラム開始直後)は何もしない
        return;
    }

    const int size = 100; //円の外接四角形の一辺の長さ

    //円の外接四角形の位置と大きさを定義
    Rectangle rect = new Rectangle(
        clickPosition.X - (size / 2),
        clickPosition.Y - (size / 2), 
        size, 
        size);

    //外接四角形に内接する位置に赤い円を描画。
    g.DrawEllipse(Pens.Red, rect);
}

 

4.グラフィックのプログラム

4-1.Graphicsクラス

はじめてグラフィックを扱うプログラムなので、Drawメソッドの中身は見慣れない記述ばかりだと思います。

まず、引数で使用している Graphicsクラス(読み方:Graphics=グラフィックス)は、Windows フォーム アプリ でのグラフィックの主役になるクラスです。このクラスには円や四角形などをいろいろなものを描画するメソッドがあります。このクラスのインスタンスは通常自分で作成するのではなく、フレームワークが自動的にインスタンスを作ってくれます。そのインスタンスはPaintイベントの引数 e のGraphicsプロパティから受け取れます。Paintイベントハンドラーの中身にたった1行 Draw(e.Graphics) と記述したのを思い出してください。これはフレームワークが生成したGraphicsクラスのインスタンスを引数に渡して Draw メソッドを呼び出すということです。

 

4-2.Clear で背景色を塗りつぶす


g.Clear(Color.Black); //背景を黒でクリアします。

1行目の g.Clear(Color.Black) はさっそく Graphicsクラスの Clear メソッド(読み方:Clear=クリア)を呼び出して、全体を黒くします。フォームのPaintイベントで生成されたGraphicsクラスが渡されてくるので、「全体」とはフォームの中身の全体を言うことになります。最大化ボタンや最小化ボタン、フォームを囲っている淵(ふち)の部分は「全体」には含まれません。

 

4-3.クリックされたか確認

if (clickPosition.X == -1)
{
    //どこもクリックされていない場合(=プログラム開始直後)は何もしない
    return;
}

次に、この Drawメソッドが呼ばれる前にどこかクリックされているか確認します。

プログラムを実行して最初にフォームを表示すると、このタイミングでフォームの内容を描画する必要があります。それで、どこもクリックしていないのPaintイベントが発生しDrawメソッドが実行されてしまいます。

今回のプログラムではクリックした位置に赤い円を表示したいので、この最初のタイミングは何も描画したくはないのです。

それを区別するために変数 clickPositionのX座標が -1 であるかを確認します。clickPositionは初期値としてXプロパティに-1を設定していますから、これが-1のままであればまだどこもクリックされていないということになります。

return を使うことで、続きのプログラムを実行せず呼び出し元(この場合、Form1_Paintに制御が戻ります。)

 

4-4.const での定数定義


const int size = 100; //円の外接四角形の一辺の長さ
    

const int size = 100; はsize という名前で「定数」(ていすう)というものを定義しています。変数の宣言に似ていますが、キーワード const (読み方:const=コンスト)をつけることで変数ではなく定数になります。変数との違いは値の変更ができないことです。値を変更する必要はないけれども名前を付けておきたい値はこのように定数にします。ここでは 円の直径を表す size という定数を定義しています。

 

4-5.DrawEllipseで円を描画

ここからグラフィックのプログラムの核心部分です。

Rectangle rect = new Rectangle(… の行はちょっと飛ばして、先に最後の g.DrawEllipseの行を見てみます。

g.DrawEllipse(Pens.Red, rect) は 円を描画します。DrawEllipseメソッド(読み方:DrawEllipse=ドロゥイリプス)が円を描画するメソッドで、第1引数は描画に使うペンを指定します。ペンは自分でいろいろ定義できますが、単純な色のペンはフレームワークであらかじめ定義されており、Pensクラス(読み方:Pens=ペンズ)の静的プロパティを使って取得できます。Redプロパティ(読み方:Red=レッド)を使って単純な赤いペンを取得しています。

第2引数で円の外接四角形を指定します。外接四角形というのは円にピッタリ接する四角形です。

たとえば、下の図では点線の四角形は赤い円の外接四角形です。

Windows フォーム アプリの画像描画ではこのように外接四角形を指定することで円の位置や大きさを決定します。外接四角形自体は描画されません。

 

4-6.外接四角形の定義

外接四角形を定義しているのが少し上の部分です。

外接四角形は Rectangle構造体(読み方:Rectangle=レクトアングル)で表現します。

//円の外接四角形の位置と大きさを定義
Rectangle rect = new Rectangle(
    clickPosition.X - (size / 2),
    clickPosition.Y - (size / 2), 
    size, 
    size);

この構造体には、Left、Top、Width、Heightというプロパティ(読み方:Left=レフト、Top=トップ、Width=ウィドゥス、Height=ハイト)というプロパティがあり、それぞれ、四角形の左上のX座標、Y座標、四角形の幅、高さを表しています。図にすると下の図のようになっています。

なお、プログラムでの座標系は学校の数学で習う座標系と Y 軸の方向が逆になります。(なので、フォームの左上が座標 (0,0) です。)

Rectangle構造体のコンストラクタを使うとこの4つの値を簡単に指定できます。このプログラムではそれを利用しています。

このプログラムでは、座標は固定ではなくクリックした位置によって変わります。クリックした位置は clickPosition変数を見ればわかるよう、FormのMouseClickイベントハンドラーで設定しています。外接四角形の一片の長さはsize定数で定義しています。

そうすると、Left の値は clickPosition の x座標から size の半分だけ左側ということになるので、 clickPosition.X - (size / 2) です。

同様に Top は clickPosition.Y - (size / 2) です。Width と Height はそのまま size です。

 

5.練習問題

問1.点を表す構造体はどれでしょうか?
Point
Rectangle
Location
問2.次のプログラムは何を表していますか?
private void Log(string message) {
    System.Diagnostics.Debug.WriteLine(message);
}
イベントハンドラー
クラス
構造体
メソッド
問3.PaintイベントのプログラムでGraphicsクラスのインスタンスを取得する一般的な方法はどれでしょうか?
コンストラクターを呼び出す
静的メソッドを使用する
の引数から取得する

 

次の回では、もうちょっとグラフィックで遊びながら、フレームワークの機能の使い方に慣れてもらいます。

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