課題の進め方
基本的には適当なプロジェクト/ソリューションを作って課題を進めた上で,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で提出物に含めたいリソースがある場合)も提出する.提出前には以下を確認しよう.
- 提出プログラムコードのファイル名は
MainForm.cs
になっているか - 提出するファイルに学籍番号と名前がコメントとして含まれているか
- 文献やWebサイトを参考にした場合は文献の情報やURLおよびアクセス日に加えて元文献のどの部分を参考にしたのかがコードの当該箇所付近のコメントとして記述されているか
- 友人と相談した・された場合はその友人の名前および大雑把な相談の内容がコメントとして書かれているか
- ただし,友人の解答を見ない,そして友人に解答を見せないようお願いします
- TA・教員と相談した場合は,その旨と大雑把な相談の内容がコメントとして書かれているか
基本課題
以下の要件を満たすお絵描きプログラムを作成せよ.
- 少なくとも以下のコントロールが配置されている
ColorPicker
NumericStepper
- 「クリア」と書かれたボタン
- お絵描き用のコントロール(ウィンドウの主要な面積を占める)
- マウスの主ボタン(右利き用マウスだと通常は左ボタン)を押してマウスを動かすと,線が動きに沿って描かれる.このとき,線の色は
ColorPicker
で選択した色で太さがNumericStepper
で選択した数であるとする.- 要はよくあるお絵描きツールの「鉛筆」ツールのような挙動をする.
- 「クリア」と書かれたボタンが押されたら,書かれた絵がクリアされる.
たとえば,以下のスクリーンショットは実装するプログラムの一例を表している.作成したアプリケーションで,この程度の絵ならばちゃんと描けるようになる(もちろんアプリケーションの挙動の意味で)ことを一つの目標にするとよい.
Tip
お絵描き用のコントロールのMouseMove
イベントを購読(イベントハンドラを登録)する.主ボタンが押されたまま移動されたかどうかは,以下のようにハンドラの第2引数のButtons
プロパティを用いて判定できる.
// oekakiControl の MouseMove イベントを購読
.MouseMove += (s, me) => {
oekakiControlif ( me.Buttons.HasFlag( MouseButtons.Primary ) ) {
// 主ボタンが押されたままマウスが移動したときの処理
}
};
Tip
素朴なアイデアはMouseMove
イベントの度に現在のマウスの位置に円を描くというものだが,それだとマウスが一度に沢山に動いたときにとぎれとぎれの「線」が描かれてしまう.それを避けるためには,以下のようにするとよいだろう.
- マウスの以前の位置を覚えておく.
- マウスの主ボタンが押されたら,その位置に円が描かれるようにし,「マウスの以前の位置」を更新する.
- マウスの主ボタンが押されたままマウスが移動されたら,「マウスの以前の位置」と「マウスの現在の位置」の間に線分が描かれるようにする.そして,「マウスの以前の位置」を更新する.
線分の描画にはGraphics.DrawLine(Pen, PointF, PointF)
メソッドが使用できるだろう.Pen
のLineCap
プロパティをPenLineCap.Round
に設定するときれいに線が引けるかもしれない(参考).
Important
繰り返すが,以下のコードはDrawable
であるd
に対し,Paint
イベントが発生したとき(≒ 描画要求があってコントロールが描画されるとき)に呼ばれる処理を追加しているのであって,描画そのものを行っているのではない.
.Paint += (s, se) => {
d// ...
};
なので,たとえば
.MouseMove += (s, me) => {
oekakiControl// ...
.Paint += (sp, pe) => {
oekakiControl// ...
};
// ...
};
のようなコードを書くと,そもそもoekakiControl.Paint += (sp, pe) => { ... }
の部分で描画は実行されないし,マウスを移動する度に描画要求があったときに実行する処理が増えていきだんだん描画処理が重くなっていく.
作成するお絵描き用コントロールをoekakiControl
とすると,そのMouseMove
イベントもPaint
イベントも購読することになるだろうが,購読する部分のコードは以下のような形になるはずだ.
.MouseMove += (s, me) => {
oekakiControl// ...
};
// ...
.Paint += (s, pe) => {
oekakiControl// ....
};
なお,oekakiControl
の(再)描画要求を投げるにはoekakiControl.Invalidate()
を呼ぶ.
発展課題
Important
本課題を完了できたのならば本課題の解答のみを提出すればよく,基本課題の解答は提出する必要はない.
基本課題の条件を満たしている限りにおいて,お絵描きプログラムにさまざまな機能を追加せよ.追加する機能は自由に決めたのでよいが,どのような機能を追加したかの説明およびプログラム上の工夫点はコメントとして提出プログラムに含めること.
どういう機能を追加したらよいかまよっている人の参考までに,いくつかの機能追加の方向性を以下に示しておく.もちろん,追加する機能はこれらに限定されない.
- お絵描きした画像を保存する機能
- 「消しゴム」機能
- アンドゥ,リドゥ機能
- Shiftを押しながらクリックすると,以前に最後にマウスを離した場所との間に直線を引く
- 適当なコントロールやダイアログ等を追加し,文字列を入力できるようにする
- 「ぼかし」などのフィルタ処理
- 拡大・縮小・回転,あるいは一般の線形変換
- レイヤ機能