Drawable
コントロール
Eto.Forms の Drawable
コントロールを利用することで,直線,矩形,楕円,文字列等いろんなものをコントロールに自由に描画できる.
そのためには,以下のようにDrawable
のPaint
イベントを購読(ハンドラを登録)するとよい.
// MainForm.cs
using System;
using Eto.Forms;
using Eto.Drawing;
// テンプレート作成時にプロジェクト名を DrawableDemo にしたことを想定(以後同様の説明を省略)
// このnamespaceは,「〜.Gtk」等にある Program.cs においても利用されるので変更するとそちら(3箇所)も変更する必要がある.
namespace DrawableDemo
{
public class MainForm : Form
{
public MainForm()
{
= "Drawable Demo";
Title = new Size(320, 420);
ClientSize
= new Drawable();
Drawable drawable // Paintを購読
// (s,pe) => { ... } の部分はPaintイベントが発生したときに呼ばれる処理であることに注意する.
// この時点でコントロールの中身がその部分の通りに描画されるわけではない.
.Paint += (s, pe) =>
drawable{
= pe.Graphics;
Graphics g
.WriteLine(Size);
Console.Clear(Colors.White);
g
// 50 px 幅の格子を描く
for (int x = 0; x < this.Size.Width; x += 50)
{
.DrawLine(Colors.LightGrey, new Point(x, 0), new Point(x, drawable.Size.Height));
g}
for (int y = 0; y < this.Size.Height; y += 50)
{
.DrawLine(Colors.LightGrey, new Point(0, y), new Point(drawable.Size.Width, y));
g}
for (int i = 0; i < 300; i += 10)
{
// 指定のペン,始点,終点で線を描く
.DrawLine(new Pen(Colors.Blue), new Point(0, 0), new Point(i, 100));
g}
// 矩形のアウトラインを描く
.DrawRectangle(Colors.Red, new RectangleF(new PointF(0, 100), new SizeF(300, 50)));
g// Colors.Red と書くかわりに Color.FromRgb(0xFF0000) で陽にRGBを指定できる
.FillRectangle(Color.FromRgb(0xFF0000), new RectangleF(new PointF(0, 150), new SizeF(300, 50)));
g
// 楕円のアウトラインを描く.Colors.Lime は Color.FromRgb(0x00FF00) と同じ色
.DrawEllipse(new Pen(Colors.Lime, 2F), new RectangleF(new PointF(0, 200), new SizeF(300, 50)));
g// 内部が塗られた楕円を描く.Colors.Green は暗めの緑
.FillEllipse(Colors.Green, new RectangleF(new PointF(0, 250), new SizeF(300, 50)));
g
// システムのデフォルトフォント,サイズ 50pt
= new Font(SystemFont.Default, 50);
Font f // テキストを指定フォントで指定座標(左上点)に描く.
.DrawText(f, Color.FromArgb(0, 0x80, 0xff, 0x80), new Point(100, 0), "文字列");
g
// PointFやRectangleFのFはfloatのF.以下はどのように表示されるかな?
// ディスプレイ(Retinaとか)によってはアンチエイリアスもかからない.
for (int i = 0; i < 5; i++)
{
= new PointF(25F + 0.5F * i, 300 + 20 * i);
PointF start = start + new PointF(0, 20);
PointF end .DrawLine(new Pen(Colors.Black, 1F), start, end);
g}
};
// Mac環境のワークアラウンド.
// Drawableの最初のPaint時にSizeが値が正しくない https://github.com/picoe/Eto/issues/2104
//
// Eto.Forms 2.6.1においては,GtkでもSizeがワンテンポ遅れて変化したりする(https://github.com/picoe/Eto/issues/1752)ので
// 現状ではSizeに依存するコードはかかないほうがよいかもしれない.
//
// Invalidate()はコントロールに再描画を促す(結果,Paintイベントを発生させる).
.SizeChanged += (s, e) => drawable.Invalidate();
drawable
= drawable;
Content
= new MenuBar();
Menu }
}
}
上記のMac(非Retinaディスプレイでの表示)での実行例(DPIの違いからWindowsだと「文字列」の部分の大きさが変わる).
上で使用したGraphics
のメソッドとその関連メソッド.
Clear(Color) |
描画領域を指定された色でクリアする. |
DrawLine(Color,PointF,PointF) |
指定された色で,与えられた始点から終点まで線を引く.なお,Point はPointF へ暗黙に型変換可能. |
DrawLine(Pen,PointF,PointF) |
指定されたペンで,与えられた始点から終点まで線を引く.Pen は色の他にたとえば太さの情報を持つ. |
DrawRectangle(Color, RectangleF) |
指定された色で,与えられた矩形のアウトラインを描く. |
DrawRectangle(Pen, RectangleF) |
上のペン版. |
DrawEllipse(Color, RectangleF) |
指定された色で,与えられた矩形に内接する楕円のアウトラインを描く. |
DrawEllipse(Pen, RectangleF) |
上のペン版. |
FillRectangle(Color, RectangleF) |
指定された色で与えられた矩形の内部を塗る. |
FillEllipse(Color, RectangleF) |
指定された色で与えられた矩形に内接する楕円の内部を塗る. |
DrawText(Font,Color,PointF,string) |
指定されたフォント,色,位置(左上)に与えられた文字列を描く. |
また,新たな型のコンストラクタ.
RectangleF(PointF, SizeF) |
指定された位置,サイズのRectangleF を作成するコンストラクタ. |
Point(int, int) |
位置を表す型,Point のコンストラクタ.x/y座標にはプロパティX とY からアクセス可. |
PointF(float, float) |
上のfloat 版. |
SizeF(float, float) |
Size のfloat 版,SizeF のコンストラクタ.幅と高さにはプロパティHeight とWidth からアクセス可. |
Pen(Color, float) |
指定された色と太さのPen を作成するコンストラクタ. |
Font(SystemFont,float) |
指定されたシステムフォント,サイズ (pt)のFont を作成するコンストラクタ.システムフォントには SystemFont.Default のほか,SystemFont.Bold や SystemFont.TitleBar などがある. |
そして,色を操作するのに用いた以下のstaticメソッドやフィールド.
Color.FromRgb(int) |
0xFF0000 (赤)などのような整数から色を作成. |
Color.FromArgb(int) |
0xFFFF0000 (不透明な赤)などのような整数から色を作成. |
Color.FromArgb(int,int,int,int) |
R, G, B, Aのそれぞれの要素(0-255)から色を作成(メソッドの名前に反して順番はRGBAなのに注意).Aは255が不透明.0は透明. |
Colors.Blue など |
規定の色を表す定数. |
なお,上記はあくまで一部である.より詳細はEto.FormsのドキュメンテーションのGraphicsのページを見るとよい.
フォント
Font(FontFamily,float)
コンストラクタを使うことで,システムにインストールされたフォントからFont
オブジェクトを作成できる.
FontFamily
インスタンスの作成にはフォント名をとるコンストラクタFontFamily(string)
を用いてもよいが,システムによってインストールされているフォントは違うのでFonts.AvailableFontFamilies
を使うのがよいかもしれない.たとえば,Fonts.AvailableFontFamilies
は以下のようにList<FontFamily>
に変換することができる.
// システムにインストールされているフォントを取得し,List<FontFamily>に変換する
<FontFamily> fontfamilies = new List<T>( Font.AvailableFontFamilies ); List
折角なので,いままで紹介していないコントロールを使って,フォントファミリとサイズを指定して,文字列を描画するプログラムを作ってみよう.具体的には DropDown
と NumericStepper
を使用する.
以下のようにすることで,Fonts.AvailableFontFamilies
が中身となるようなドロップダウンを作成できる.
= new DropDown();
DropDown fontfamilyChooser // ドロップダウンの「中身」を Fonts.AvailableFontFamilies にセットする
.DataStore = Fonts.AvailableFontFamilies;
fontfamilyChooser// ドロップダウンの要素をどのようにして表示するための文字列にするかを規定する(詳細は説明しない)
.ItemTextBinding = Binding.Delegate((object o) => o.ToString()); fontfamilyChooser
フォントのサイズの選択にはNumericStepper
を使うことにする.これは,数値を選択するためのコントロールであり,数字の横に上三角形と下三角形が表示されている.きっとよく見るようなものであると思われる.注意事項としては選択される数等はdouble
型であるという点である.
= new NumericStepper
NumericStepper sizeSelector {
= 5, // 最小値
MinValue = 150, // 最大値
MaxValue = 1, // 増分
Increment = 25 // 今の値
Value };
また,描画するテキストもユーザが入力できるようにする.これはこれまでも出てきたTextBox
を使う.
= new TextBox { PlaceholderText = "Text to Draw" }; TextBox textToDraw
そして,テキストを描画するためのDrawable
を準備する.
= new Drawable();
Drawable d
.Paint += (s, pe) =>
d{
.Graphics.Clear(Colors.White);
pe// fontfamilyChooser.SelectedValue は FontFamily のインスタンスであるはずなのでキャストする.
// なにも選ばれていない状態だと fontfamilyChooser.SelectedValue は null だが,その場合は FontFamily 型の null となる.
= (FontFamily) fontfamilyChooser.SelectedValue;
FontFamily ff if (ff != null)
{
// sizerSelector.Value は double なので int に丸める.
// 注:Fontコンストラクタの第2引数はfloatなので,floatにキャストしたのでも十分ではある.
.Graphics.DrawText(new Font(ff, (int)sizeSelector.Value), Colors.Black, new PointF(0, 0), textToDraw.Text);
pe}
};
最後にfontfamilyChooser
,sizeSelector
,textToDraw
の内容に変更があったときに,d
を再描画するようにする.そうでなければ,リサイズ等の他に更新すべき理由がない限り描画領域は更新されない.
// Invalidate() はコントロールに再描画を促す
// これらをコメントアウトしてみるとどのような挙動になるだろうか?
.SelectedIndexChanged += (s, e) => d.Invalidate();
fontfamilyChooser.ValueChanged += (s, e) => d.Invalidate();
sizeSelector.TextChanged += (s, e) => d.Invalidate(); textToDraw
あとは適当にこれらのコンポーネントは配置すればよい.まとめるとMainForm.cs
への変更は以下のようになる.
// MainForm.cs
using System;
using Eto.Forms;
using Eto.Drawing;
namespace FontDemo
{
public class MainForm : Form
{
public MainForm()
{
= "Font Demo";
Title = new Size(200, 200);
MinimumSize
= new DropDown { };
DropDown fontfamilyChooser .DataStore = Fonts.AvailableFontFamilies;
fontfamilyChooser.ItemTextBinding = Binding.Delegate((object o) => o.ToString());
fontfamilyChooser
= new NumericStepper
NumericStepper sizeSelector {
= 5,
MinValue = 150,
MaxValue = 1,
Increment = 25
Value };
= new TextBox { PlaceholderText = "Text to Draw" };
TextBox textToDraw
= new Drawable();
Drawable d .Paint += (s, pe) =>
d{
.Graphics.Clear(Colors.White);
pe= (FontFamily)fontfamilyChooser.SelectedValue;
FontFamily ff if (ff != null)
{
.Graphics.DrawText(new Font(ff, (int)sizeSelector.Value), Colors.Black, new PointF(0, 0), textToDraw.Text);
pe}
};
.SelectedIndexChanged += (s, e) => d.Invalidate();
fontfamilyChooser.ValueChanged += (s, e) => d.Invalidate();
sizeSelector.TextChanged += (s, e) => d.Invalidate();
textToDraw
= new StackLayout
Content {
= 10,
Padding = 5,
Spacing = HorizontalAlignment.Stretch,
HorizontalContentAlignment =
Items {
new StackLayout
{
= Orientation.Horizontal,
Orientation = {
Items , sizeSelector
fontfamilyChooser}
},
,
textToDrawnew StackLayoutItem (d, true)
}
};
= new MenuBar();
Menu }
}
}
上記のMac(非Retinaディスプレイでの表示)での実行例は以下となる(起動直後ではなく,リサイズやテキストの変更などを行った状態).
マウスイベントの処理:クリックした位置に円を描く
マウスイベントを購読するには,MouseDown
等にハンドラを登録すればよい.
イベント | 説明 |
---|---|
MouseDown |
マウスボタンの押下. |
MouseUp |
マウスボタンを押したのを離す.クリックを取りたいなら,これか上のを使う(実際のアプリケーションでも,押下時に反応するボタンと,離したときに反応するボタンがあるようである). |
MouseEnter |
マウスポインタがそのコントロールの領域に入る. |
MouseLeave |
マウスポインタがそのコントロールの領域から出る. |
MouseWheel |
ホイールを回す.縦とは限らない. |
MouseMove |
マウスポインタがそのコントロールの領域内で動く. |
MouseDoubleClick |
ダブルクリック. |
これらに渡すハンドラの第2引数はMouseEventArgs
になる.たとえば以下のプロパティを通じて,イベント発生時のマウスの状態を取得できる.
Buttons |
マウスのボタン(Buttons.Primary やButtons.Alternate など.同時押しもOK) |
Delta |
SizeF 型.ホイールの増分 |
Location |
PointF 型.ポインタの位置. |
Modifiers |
修飾キー.(Keys.Control やKeys.Alt など) |
なお,押されたボタンがどれかはHasFlag
を使えばよい.たとえば,以下のように.
+= (s, me) => {
MouseUp if ( me.Buttons.HasFlag(Buttons.Primary) )
{
// 左クリック(左利き用設定をしていない場合)のときの処理
}
else if ( me.Buttons.HasFlag(Buttons.Alternate) )
{
// 右クリックのときの処理
}
}
では,マウスイベントのハンドリングの例として,マウスのクリック位置に円を描くプログラムを作ってみよう.Drawable
もコントロールなので,そのMouseDown
イベントを購読すればよい.たとえば,以下のように.
// MainForm.cs
using System;
using Eto.Forms;
using Eto.Drawing;
namespace MouseDemo
{
public class MainForm : Form
{
public MainForm()
{
= "Mouse Demo";
Title = new Size(200, 200);
MinimumSize
= new PointF(0, 0);
PointF clicked = new SizeF(10, 10);
SizeF circleSize
= new Drawable();
Drawable d .Paint += (s, pe) =>
d{
// 演算子のオーバローディングにより,floatをSizeFに掛けることもできるし,PointFにSizeFを加減算可能.
.Graphics.FillEllipse(Colors.Black, new RectangleF(clicked - 0.5F * circleSize, clicked + 0.5F * circleSize));
pe};
.MouseDown += (s, me) =>
d{
= me.Location;
clicked .Invalidate();
d};
= d;
Content = new MenuBar();
Menu }
}
}
たとえばMacにおける,上(を含むプログラム)の実行例は以下となる.
円の色を選択できるように
作成したプログラムを少し拡張して,色を選べるようにしてみよう.この目的にはColorPicker
というコントロールが利用可能である.このコントロールはクリックすると専用のダイアログ(プラットフォーム毎に異なる)を表示し,ユーザが色を選ぶことを可能にする.
より具体的には,ColorPicker
により次にクリックしたときに表示される円の色を指定できるようする.そのためには,中心の座標のみならず,塗るべき色も覚えておけばよい.そのために,以下のクラスを準備する.
// 色と中心点の組
class ColoredCircle
{
// 中心点
public PointF Center { get; set; }
// 色
public Color Color { get; set; }
public ColoredCircle(PointF center, Color col)
{
= center;
Center = col;
Color }
// 描画法
public void Draw(Graphics g)
{
= new SizeF(10, 10); // サイズは今のところ決め打ち
SizeF circleSize .FillEllipse(Color, new RectangleF(Center - 0.5F * circleSize, Center + 0.5F * circleSize));
g}
}
PointF
型のclicked
の代わりに,ColoredCircle
型の変数を用意し,Drawable
のPaint
イベントのハンドラは,その変数がnullでなければ,指しているインスタンスのDraw
を呼ぶようにする.
= null;
ColoredCircle cc = new Drawable();
Drawable d .Paint += (s, pe) =>
d{
if (cc != null) { cc.Draw(pe.Graphics); }
};
ColorPicker
の作成はとても簡単である.たとえば,初期カラーが赤であるようなカラーピッカーは以下のようにして作成できる.
= new ColorPicker { Value = Colors.Red }; ColorPicker colorPicker
あとは,描画領域がクリックされたときに,クリックされた座標とカラーピッカーの色からColoredCircle
を生成するようにすればよい.
.MouseDown += (s, me) =>
d{
= new ColoredCircle(me.Location, colorPicker.Value);
cc .Invalidate();
d};
全体ではコード(MainForm.cs
の部分のみ)は以下となる.
// MainForm.cs
using System;
using Eto.Forms;
using Eto.Drawing;
namespace MouseDemo2
{
class ColoredCircle
{
public PointF Center { get; set; }
public Color Color { get; set; }
public ColoredCircle(PointF center, Color col)
{
= center;
Center = col;
Color }
public void Draw(Graphics g)
{
= new SizeF(10, 10);
SizeF circleSize .FillEllipse(Color,
gnew RectangleF(Center - 0.5F * circleSize, Center + 0.5F * circleSize));
}
}
public class MainForm : Form
{
public MainForm()
{
= "Mouse Demo 2";
Title = new Size(200, 200);
MinimumSize
= null;
ColoredCircle cc
= new Drawable();
Drawable d .Paint += (s, pe) =>
d{
if (cc != null) { cc.Draw(pe.Graphics); }
};
= new ColorPicker { Value = Colors.Red };
ColorPicker colorPicker .MouseDown += (s, me) =>
d{
= new ColoredCircle(me.Location, colorPicker.Value);
cc .Invalidate();
d};
= new StackLayout
Content {
= 5,
Padding = 5,
Spacing = HorizontalAlignment.Stretch,
HorizontalContentAlignment = {
Items ,
colorPickernew StackLayoutItem( d, true )
}
};
= new MenuBar();
Menu }
}
}
上のコードのMacでの実行例たとえば以下となる.
複数個の円を描く
さて,円が一個だけでは寂しいのでもっとたくさん描画できるようしたい.そこで,クリックするたびに円を描くようにしたい.この実現方法は2通りある.
ColoredCircle
のリストを覚えておき,Paint
イベントのハンドラはそれらすべてを描画する.- 画像を保持しておきクリックをしたら
ColoredCircle
をそこに描く.Paint
イベントのハンドラは画像を表示する.
円のリストを覚える方法
前者の拡張はとても直截である.一つのColoredCircle
でなく,ColoredCircle
のリストを保持するようにすればよい.上のコードに対する変更はわずか5箇所(うち一つはnamespaceの名前)だ.
// MainForm.cs
using System;
using System.Collections.Generic; // 追加
using Eto.Forms;
using Eto.Drawing;
namespace MouseDemo3 // 変更箇所
{
class ColoredCircle
{
public PointF Center { get; set; }
public Color Color { get; set; }
public ColoredCircle(PointF center, Color col)
{
= center;
Center = col;
Color }
public void Draw(Graphics g)
{
= new SizeF(10, 10);
SizeF circleSize .FillEllipse(Color, new RectangleF(Center - 0.5F * circleSize, Center + 0.5F * circleSize));
g}
}
public class MainForm : Form
{
public MainForm()
{
= "Mouse Demo 3";
Title = new Size(200, 200);
MinimumSize
<ColoredCircle> ccs = new List<ColoredCircle>(); // 変更箇所
List
= new Drawable();
Drawable d .Paint += (s, pe) =>
d{
foreach (var cc in ccs) { cc.Draw(pe.Graphics); } // 変更箇所
};
= new ColorPicker { Value = Colors.Red };
ColorPicker colorPicker .MouseDown += (s, me) =>
d{
.Add(new ColoredCircle(me.Location, colorPicker.Value)); // 変更箇所
ccs.Invalidate();
d};
= new StackLayout
Content {
= 5,
Padding = 5,
Spacing = HorizontalAlignment.Stretch,
HorizontalContentAlignment = {
Items ,
colorPickernew StackLayoutItem( d, true )
}
};
= new MenuBar();
Menu }
}
}
上のコードのMacにおける実行例は以下となる.
画像を保持する方法
ビットマップイメージ(Bitmap
)を保持し,マウスクリック時にはイメージのほうに円を描き,Drawable
のPaint
ではその画像を単に表示するという方法もある.その場合は,少しプログラムが複雑になるので,Drawable
に関連するイベント処理を,派生クラスのコンストラクタにまとめてしまうのがよいだろう.
// MainForm.cs
using System;
using Eto.Forms;
using Eto.Drawing;
namespace MouseDemo4
{
class DrawableImage : Drawable
{
private Bitmap bitmap;
public Color CurrentColor { get; set; }
public DrawableImage()
{
// 200x200のビットマップを作成.透明度付き
// PixelFormat.Format32bppRgba は1画素が32ビット(4バイト)で表されていて,
// 上位から順に アルファ,赤,緑,青 がそれぞれ8ビット(1バイト)ずつ並んでいることを表す.
= new Bitmap(200, 200, PixelFormat.Format32bppRgba);
bitmap
+= (s, pe) =>
Paint {
// bitmap を描画するだけ
.Graphics.DrawImage(bitmap, new PointF(0, 0));
pe};
+= (s, me) =>
MouseDown {
= new SizeF(10, 10);
SizeF circleSize = me.Location;
PointF center // ビットマップに円を描画する.
using (Graphics g = new Graphics(bitmap))
{
.FillEllipse(CurrentColor, new RectangleF(center - 0.5F * circleSize, center + 0.5F * circleSize));
g}
Invalidate();
};
+= (s, e) =>
SizeChanged {
// リサイズ時に適当にビットマップイメージのサイズを拡大する.
if (bitmap.Height < ClientSize.Height || bitmap.Width < ClientSize.Width)
{
= bitmap;
Bitmap oldImg // 毎回毎回リサイズで描画領域が増えるごとにコピーするのはコストが大きいので,50%分の余裕をもたせる.
//
= Size.Max(ClientSize, oldImg.Size);
Size newSize += Size.Max(newSize / 2, new Size(1, 1));
newSize = new Bitmap(newSize, PixelFormat.Format32bppRgba);
Bitmap newImg using (Graphics g = new Graphics(newImg))
{
// 古いイメージをコピー
.DrawImage(oldImg, new PointF(0, 0));
g}
= newImg;
bitmap .Dispose();
oldImgInvalidate();
}
};
}
}
public class MainForm : Form
{
public MainForm()
{
= "Mouse Demo 4";
Title = new Size(200, 200);
MinimumSize
= new ColorPicker { Value = Colors.Red };
ColorPicker colorPicker = new DrawableImage { CurrentColor = colorPicker.Value };
DrawableImage d
.ValueChanged += (s, e) =>
colorPicker{
.CurrentColor = colorPicker.Value;
d};
= new StackLayout
Content {
= 5,
Padding = 5,
Spacing = HorizontalAlignment.Stretch,
HorizontalContentAlignment = {
Items ,
colorPickernew StackLayoutItem( d, true )
}
};
= new MenuBar();
Menu }
}
}
実行例は上と変わらないので割愛.