課題の進め方

基本的には適当なプロジェクト/ソリューションを作って課題を進めた上で,MainForm.csを提出する.

採点はコマンドラインからdotnet new etoappを実行した上でMainForm.csを提出されたもので上書きすることにより行う. 採点者は基本的に採点者自身の環境で動作確認を行うことに注意する.

第6回課題においてMainForm.cs以外に提出に含めたいものがある場合(たとえば画像ファイル等)は事前にメール(メールアドレスはClassroom内「受講者用案内」を参照)で相談のこと.

他の人(受講者・非受講者両方)に解答内容(一部でも)を見せない,そして他の人の解答内容(一部でも)を見ないようお願いします.特に, 公開の場所に解答を置かないようお願いします.githubやbitbucket等は使える人は使えばよいと思いますが,privateレポジトリにするようにお願いします.

プロジェクト/ソリューションの作成

Note

再掲:プロジェクトは一つの実行形式やライブラリを作成するためのコード等を全てまとめたものであり,ソリューションは関連するプロジェクトをまとめたもの(参考:What are solutions and projects in Visual Studio?).

指定された名前(課題5ではQ5,課題6ではQ6とする)の空のフォルダを適当な場所に新規作成し, VSCodeで作成したフォルダを開く.そして,VSCode内のターミナルで以下を実行する.

dotnet new etoapp -sln

課題の実施

MainForm.cs を問題文の指示の通りに編集する(提出・採点手続きの簡略化のため提出する.csファイルは一つのみ).作成した.csファイルには先頭部分に学籍番号と名前をコメントとして含めること.また,自身のプログラムの動作確認を行ったプラットフォームの情報(Mac, Gtk, Wpfの別.複数可.わからないならOS名)も含めるものとする.こちらの情報はあくまで念のためであり,基本的には採点者は自身の環境で動作を確認を行う.なので,たとえば学籍番号Z0TB9999の東北 大学さんの提出ファイルは,もし当人が動作確認をプロジェクト名.Macを用いて行ったのであれば

// Z0TB9999
// 東北 大学
// 動作確認:Mac 

という行から始まる.

提出

できあがった MainForm.csをClassroom内の当該回の「課題」より提出する.また問題文に指示がある場合はそのファイル(例:課題6で提出物に含めたいリソースがある場合)も提出する.提出前には以下を確認しよう.

基本課題

以下の要件を満たすお絵描きプログラムを作成せよ.

たとえば,以下のスクリーンショットは実装するプログラムの一例を表している.作成したアプリケーションで,この程度の絵ならばちゃんと描けるようになる(もちろんアプリケーションの挙動の意味で)ことを一つの目標にするとよい.

作成を目指すプログラムのスクリーンショット

Tip

お絵描き用のコントロールのMouseMoveイベントを購読(イベントハンドラを登録)する.主ボタンが押されたまま移動されたかどうかは,以下のようにハンドラの第2引数のButtonsプロパティを用いて判定できる.

// oekakiControl の MouseMove イベントを購読
oekakiControl.MouseMove += (s, me) => {
   if ( me.Buttons.HasFlag( MouseButtons.Primary ) ) {
       // 主ボタンが押されたままマウスが移動したときの処理
   }
};

Tip

素朴なアイデアはMouseMoveイベントの度に現在のマウスの位置に円を描くというものだが,それだとマウスが一度に沢山に動いたときにとぎれとぎれの「線」が描かれてしまう.それを避けるためには,以下のようにするとよいだろう.

  • マウスの以前の位置を覚えておく.
  • マウスの主ボタンが押されたら,その位置に円が描かれるようにし,「マウスの以前の位置」を更新する.
  • マウスの主ボタンが押されたままマウスが移動されたら,「マウスの以前の位置」と「マウスの現在の位置」の間に線分が描かれるようにする.そして,「マウスの以前の位置」を更新する.

線分の描画にはGraphics.DrawLine(Pen, PointF, PointF)メソッドが使用できるだろう.PenLineCapプロパティをPenLineCap.Roundに設定するときれいに線が引けるかもしれない(参考).

Important

繰り返すが,以下のコードはDrawableであるdに対し,Paintイベントが発生したとき(≒ 描画要求があってコントロールが描画されるとき)に呼ばれる処理を追加しているのであって,描画そのものを行っているのではない

d.Paint += (s, se) => {
    // ...
};

なので,たとえば

oekakiControl.MouseMove += (s, me) => {
    // ... 
    oekakiControl.Paint += (sp, pe) => {
        // ...
    };
    // ...
};

のようなコードを書くと,そもそもoekakiControl.Paint += (sp, pe) => { ... }の部分で描画は実行されないし,マウスを移動する度に描画要求があったときに実行する処理が増えていきだんだん描画処理が重くなっていく.

作成するお絵描き用コントロールをoekakiControlとすると,そのMouseMoveイベントもPaintイベントも購読することになるだろうが,購読する部分のコードは以下のような形になるはずだ.

oekakiControl.MouseMove += (s, me) => {
    // ...
};
// ...
oekakiControl.Paint += (s, pe) => {
    // ....
};

なお,oekakiControlの(再)描画要求を投げるにはoekakiControl.Invalidate()を呼ぶ.

発展課題

Important

本課題を完了できたのならば本課題の解答のみを提出すればよく,基本課題の解答は提出する必要はない.

基本課題の条件を満たしている限りにおいて,お絵描きプログラムにさまざまな機能を追加せよ.追加する機能は自由に決めたのでよいが,どのような機能を追加したかの説明およびプログラム上の工夫点はコメントとして提出プログラムに含めること.

どういう機能を追加したらよいかまよっている人の参考までに,いくつかの機能追加の方向性を以下に示しておく.もちろん,追加する機能はこれらに限定されない.