PCの使い方やプログラミングの解説をしている動画で強調したい部分をドラッグすると枠が表示され、しばらくすると消えていきます。これ、どうやって作っているのでしょうか? それとも既存のアプリをつかっているのでしょうか? ここはブログネタになるので自作します。
Contents
透明なフォームに矩形を描画?
作り方ですが、FormクラスにはOpacityというプロパティがあり、これを0に近づけることで透明に近づけることができます。そこでディスプレイ全体を透明なフォームで覆い、そこに矩形を描画すれば好きなところに矩形を描画できそうです。
ところがこのやり方ではうまくいきません。フォームの背景色を透明に近づければ近づけるほどそこの描画された矩形も透明になってしまうのでなにも表示されません。クリックしても反応しないイタズラアプリにしかなりません。
ではどうするか? 以前のアップした記事 デスクトップに現在時刻を描画する ではFormクラスの背景色をTransparencyKeyプロパティで指定した色と同じすることで貫通するウィンドウをつくりました。Opacityは1.0にした状態(まったく透過しない)で矩形描画はそのままできて、それ以外の部分はTransparencyKeyプロパティで指定した色にすることで透明にすることができるのではないでしょうか?
ではさっそく作成してみましょう。
Form1クラス
親になるフォームとその子になるフォームをつくります。親フォームで設定(矩形の線の色や太さ、消えるまでの時間)を設定して子フォームに矩形を描画します。
親になるフォーム(Form1クラス)をこんな感じでつくります。
子フォーム(Form2クラス)はデザイナで設定することは特にありません。
初期化
Form1クラスのコンストラクタを示します。
ここでやっていることは設定のために使うNumericUpDownコントロールの初期値と最大値、最小値の設定です。ドラッグしたときに表示される色の初期化(はじめは黒)です。このフォームは他のウィンドウの後ろに隠れてしまうとアプリケーションとしては使いにくいので最前面に表示されるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { Form2 _form2 = new Form2(); public Form1() { InitializeComponent(); this.TopMost = true; Color color = Color.FromArgb(255, 0, 0, 0); pictureBox1.BackColor = color; numericUpDownLineWidth.Value = 1; numericUpDownLineWidth.Minimum = 1; numericUpDownLineWidth.Maximum = 20; numericUpDownSeconds.Value = 0; numericUpDownSeconds.Minimum = 0; numericUpDownSeconds.Maximum = 10 * 1000; numericUpDownSeconds.Increment = 100; } } |
色の設定
ドラッグしたときに描画される矩形の色を設定するときのボタンをクリックしたときの処理を示します。ColorDialogを表示して[OK]がクリックされたときはその色をPictureBoxの背景色として設定して現在どの色が選択されているのかがわかるようにしています。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { ColorDialog _colorDialog = new ColorDialog(); private void button2_Click(object sender, EventArgs e) { if (_colorDialog.ShowDialog() == DialogResult.OK) { pictureBox1.BackColor = _colorDialog.Color; } } } |
[ドラッグしたところに矩形を表示させる]ボタンがクリックされたときの処理を示します。選択されている色と線の太さ、消失までの時間、矩形が消えるときだんだん薄くなりながら消えていくのかどうかはForm1に配置されているコントロールの状態をみればわかるので、その情報をすでに生成されているForm2クラスのインスタンスに渡して表示させています。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { _form2.LineWidth = (int)numericUpDownLineWidth.Value; _form2.LineColor = pictureBox1.BackColor; _form2.Millisecond = (int)numericUpDownSeconds.Value; _form2.IsFadeout = checkBox1.Checked; _form2.Show(); } } |
Form2クラス
次に矩形が描画される透明なフォームを表示するForm2クラスを示します。
初期化
コンストラクタを示します。描画される透明なフォームにはタイトルバーは不要なのでFormBorderStyleプロパティはFormBorderStyle.Noneを指定します。最大化、最小化ボタンも不要です。表示されるときは最大サイズで、Opacityプロパティは透明に見えるように0に近い値とします。またこれも最前面に表示されるフォームとします。
描画される矩形の線が消滅するときだんだん薄くなっていくようにするためにタイマーを使用しています。そのためコンストラクタではタイマーの初期化もおこなっています。
フィールド変数 _lineLifeが0になるとフォームそのものが非表示になります。_lineLifeMaxは_lineLifeが取り得る最大値です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public partial class Form2 : Form { Timer _timer = new Timer(); int _lineLifeMax = 0; int _lineLife = 0; const double _opacity = 0.01; public Form2() { InitializeComponent(); this.WindowState = FormWindowState.Maximized; this.FormBorderStyle = FormBorderStyle.None; this.MinimizeBox = false; this.MaximizeBox = false; this.TopMost = true; this.BackColor = Color.White; this.Opacity = _opacity; _timer.Interval = 100; _timer.Tick += Timer_Tick; _timer.Start(); _lineLife = _lineLifeMax; this.DoubleBuffered = true; } } |
Form2クラスのプロパティを示します。Millisecondは描画された矩形が消滅するまでのミリ秒です。このプロパティが変更されるときは消滅するまでの時間が変更されることを意味するので_lineLifeMaxも変更されなければなりません。
LineColorプロパティは描画される矩形の色、LineWidthプロパティは描画される線の太さ、IsFadeoutは消滅するときだんだん薄くなって消滅するのかどうかです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public partial class Form2 : Form { int _millisecond = 0; public int Millisecond { get { return _millisecond; } set { _millisecond = value; _lineLifeMax = _millisecond / _timer.Interval; } } public Color LineColor { set; get; } public int LineWidth { set; get; } public bool IsFadeout { set; get; } } |
ドラッグ開始時の処理
透明に近い背景色のフォームがクリックされたときの処理を示します。クリックされたら矩形のスタート地点(左上の座標とは限らない)をフィールド変数の_rectangleStartXと_rectangleStartYに保存します。このふたつを-1で初期化していますが、これはこの値が-1のときは矩形は描画されないということを意味しています。またフィールド変数 _isRectangleFixedがtrueのときはドラッグが終わっていて矩形の位置とサイズが変更されないことを意味しています。
このフォームは矩形のスタート地点が確定するまえはどこでもクリックできる透明に近いフォームです。しかし透明なままでは矩形が描画されても見えないので、クリックされた場合はOpacityプロパティを1.0(不透過)にしてTransparencyKeyプロパティを白に近い色にして、背景色も同じ色にします。
本当にTransparencyKeyプロパティと背景色を白にしてしまうとクリックされる前の背景色と完全に同じになり、クリックすることができなくなってしまうので、このようにしています。これで描画される矩形の線の色は鮮明にみえるようになります。
またここで矩形を描画するためのPenオブジェクトを生成します。古いオブジェクトがある場合はDisposeメソッドで破棄します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form2 : Form { int _rectangleStartX = -1; int _rectangleStartY = -1; bool _isRectangleFixed = false; Rectangle _rectangle = new Rectangle(); Pen _pen = new Pen(Color.Black, 1); protected override void OnMouseDown(MouseEventArgs e) { _rectangleStartX = e.X; _rectangleStartY = e.Y; this.Opacity = 1.0; this.TransparencyKey = Color.FromArgb(255, 254, 254, 254); this.BackColor = Color.FromArgb(255, 254, 254, 254); _pen.Dispose(); _pen = new Pen(LineColor, LineWidth); base.OnMouseDown(e); } } |
ドラッグされているときの処理
ドラッグされているときの処理を示します。_rectangleStartX が -1 ではないときはマウスボタンがおされたまま移動している(ドラッグされている)状態です。この場合はそのときのマウスの座標と保存しておいた_rectangleStartXと_rectangleStartYと比較して描画すべき矩形の左上の座標と幅、高さを算出します。そしてRectangleオブジェクトを生成して、これを_rectangleに格納します。そしてInvalidateメソッドを呼び出しています。これで矩形が描画されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form2 : Form { protected override void OnMouseMove(MouseEventArgs e) { if (_rectangleStartX != -1 && !_isRectangleFixed) { int width = Math.Abs(e.X - _rectangleStartX); int height = Math.Abs(e.Y - _rectangleStartY); int x = Math.Min(e.X, _rectangleStartX); int y = Math.Min(e.Y, _rectangleStartY); _rectangle = new Rectangle(x, y, width, height); } Invalidate(); base.OnMouseMove(e); } } |
ドラッグ終了時の処理
ドラッグされている状態からマウスボタンが離されたときの処理を示します。
このときに矩形の位置とサイズが確定します。そこでフィールド変数 _isRectangleFixedをtrueにします。それとともに_lineLifeに_lineLifeMaxを代入します。これで描画されている矩形がいつ消えるのかも確定します。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form2 : Form { protected override void OnMouseUp(MouseEventArgs e) { _isRectangleFixed = true; _lineLife = _lineLifeMax; base.OnMouseUp(e); } } |
矩形を消滅させる処理
Timer.Tickイベントが発生したときの処理を示します。
ドラッグが終了してマウスボタンが離されている場合は、フィールド変数 _isRectangleFixedにはtrueが格納されています。この場合は_lineLifeをデクリメントします。そしてIsFadeoutプロパティがtrueの場合、フォームの透過度を変更してだんだん消えていくように見せないといけないので、Opacityプロパティに設定する値を計算します。
もし_lineLifeかOpacityに設定しようとしている値が0以下の場合はフォームそのものを非表示にします。また次回に呼び出されることに備えて初期化の処理(クリックされる前の状態に戻す)をおこないます。それ以外の時はフォードアウトするときは新しいOpacityプロパティの値を設定し、それ以外の時はなにもしません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public partial class Form2 : Form { private void Timer_Tick(object sender, EventArgs e) { if (_isRectangleFixed) { _lineLife--; double opacity = (double)_lineLife / _lineLifeMax; if (_lineLife <= 0 || opacity <= 0) { _isRectangleFixed = false; _rectangleStartX = -1; _rectangleStartY = -1; _rectangle = new Rectangle(); this.BackColor = Color.White; this.Opacity = _opacity; this.Visible = false; return; } if (IsFadeout) this.Opacity = opacity; } } } |
描画の処理
矩形を描画するときの処理を示します。_rectangleStartXが-1でないときは矩形の描画処理をおこないます。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form2 : Form { protected override void OnPaint(PaintEventArgs e) { if (_rectangleStartX != -1) e.Graphics.DrawRectangle(_pen, _rectangle); base.OnPaint(e); } } |