宇宙仮面の
C# Programming

 
Image

☆型のボタンを Button から継承して作る

開発環境: Visual Studio 2003 

1.目次

2.目的

グラデーションボタン、LEDランプがあるボタンに引き続き、Button コントロールを継承して☆型のボタンを作りました。

どうして、こうもデザインセンスがないのかと思う。。^^;

3.参考

(1) http://www.codeproject.com/useritems/LEDRadioButton.asp
(2) LEDランプがあるボタン

4.作り方


作り方は基本的にグラデーションのあるボタンと同じです。

1.Star Button のソースコードをコンパイルします。
2.次に、ツールバーのカスタマイズにより、コンパイルしたアセンブリーをツールバーに登録します。
3.ツールバーに StarButton のアイコンが現れるので、それを Windows.Form 上に Drag&Drop します。
4.starButton インスタンスのプロパティで、ボタンの色などの属性をセットします。

Image


あとは、実行すると 次のように ☆型のボタンが現れます。

マウスがボタンの外にある場合。
Image


マウスがボタンの上に来た場合。
Image


マウスでボタンを押した場合。
Image


5.解説


ポイント1 ボタンから継承します。

これにより、ボタンの基本的な機能はすべて System.Windows.Forms.Button の物を使い、ボタンの形、色、押されたときの動作などの必要最低限のところだけを継承したクラス(StarButton)で実装します。

public class StarButton : System.Windows.Forms.Button

...

ポイント2 デザイン時に変更したい値(ボタンの色など)は、プロパティにして、アトリビュートを追加します。

継承した StarButton を標準のボタンと同じようにフォームデザイナーで扱えます。
このときに、ボタンの色など、デザイン時に変更したい値は、プロパティにすることにより、あとから値を変更できます。

また、アトリビュートを追加します。
これにより、フォームデザイナーのプロパティでの分類、説明、デフォルト値を設定することができ、デザイン時の見栄えが良くなります。(なくてもOK)

このためには、System.ComponetMode 名前空間が必要です。
次は例です。

using System.ComponentModel;
....
                [Category( "Star Button" )]
                [Description( "境界の色" )]
                [DefaultValue( typeof(Color), "HotTrack" )]
                public Color BorderColor 
                {
                        get 
                        {
                                return this.borderColor;
                        }
                        set 
                        {
                                this.borderColor = value;
                                // cause a repaint if necessary
                                if (this.IsHandleCreated && this.Visible) 
                                {
                                        Invalidate();
                                }
                        }
                }

ポイント3 ☆型のボタンにする方法

ボタンの OnPaint が呼ばれたときに、独自の描画(☆のボタンを描く)ことをしてやれば、☆のボタンになります。そのメインルーチンが DrawButton()です。

この中でやっていることは、まずマウスがボタンの上にあるのか、ないのか、ある場合はマウスのボタンが押された状態にあるのかどうかでボタンの色を決定します。 次に、ボタンの形の☆型の GraphicsPath を計算します。これは、GetPath() というメソッドを作っています。 次に、☆を描画します。ここでは、☆の塗りつぶしと、境界線を描画しています。 このままだと、もともとのボタンの形が長方形なので、☆の外側を押した場合にもボタンを押すことができてしまいます。 そこで、ボタンのリージョンを☆型にします。そのために、Region に先ほど作った ☆型のpath を代入します。 ポイントとしては、さらにこのボタンから継承する場合に備えて、protected にしておきます。 protected void DrawButton(Graphics gr) { // ボタンの表示色を決定する。 Color currentColor; currentColor = isMouseOver ? this.mouseOvertColor : this.BackColor; currentColor = isMouseDown ? this.mouseDownColor : currentColor; // Star の Path を計算する。 GraphicsPath starPath = this.GetPath(); // Star を描画する Brush bgBrush = new SolidBrush( currentColor ); gr.FillPath(bgBrush, starPath); // Star の境界線を描く Pen pen = new Pen(BorderColor, 2.0F); gr.DrawPath(pen, starPath); // ボタンのリージョンを Star にする。 this.Region = new System.Drawing.Region(starPath); }

ポイント4 ☆型の Path を作る。

☆型の GraphicsPath を作ります。
ここのパスを変えるだけで、いろいろな形のボタンが作れます。

フォームデザイナーでボタンの大きさを編集することができます。
この場合、ボタンの大きさに合わせて、この☆の大きさを変更する必要があります。
そこで、int r = System.Math.Min(this.Width, this.Height)/2; により、
ボタンの短辺の大きさを取得し、それをベースに☆を描画するようにします。

パスの作り方はいろいろあるので、ここでは特に説明しません。

さらにこのボタンから継承する場合に備えて、protected にしておきます。

                /// <summary>
                /// 星型の Path を作る。
                /// </summary>
                /// <returns></returns>
                protected GraphicsPath GetPath()
                {
                        GraphicsPath gPath = new GraphicsPath();
                        int r = System.Math.Min(this.Width, this.Height)/2;

                        Point[] points = 
                                                {
                                                        new Point((int)(r*Math.Sin(Math.PI*2*0.0)), (int)(r*-Math.Cos(Math.PI*2*0))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*1/5)), (int)(r*-Math.Cos(Math.PI*2*1/5))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*2/5)), (int)(r*-Math.Cos(Math.PI*2*2/5))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*3/5)), (int)(r*-Math.Cos(Math.PI*2*3/5))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*4/5)), (int)(r*-Math.Cos(Math.PI*2*4/5))),
                        };

                        gPath.FillMode = System.Drawing.Drawing2D.FillMode.Winding;
                        gPath.AddLine(points[0], points[2]);
                        gPath.AddLine(points[2], points[4]);
                        gPath.AddLine(points[4], points[1]);
                        gPath.AddLine(points[1], points[3]);
                        gPath.AddLine(points[3], points[0]);

                        // 中心点を星の中心へ移す。
                        gPath.Transform(new System.Drawing.Drawing2D.Matrix(1,0,0,1,r,r));
                        return gPath;
                }

ポイント5 OnPaint() をオーバーライドする。

ボタンを描画するときに OnPaint() が呼ばれます。
こいつをオーバーライドして、DrawButton()で☆を書くことにより、☆型のボタンにします。
ベースクラスの OnPaint() は呼ばないようにします。

                protected override void OnPaint(PaintEventArgs pe) 
                {
                        // base.OnPaint(pe);
                        this.DrawButton(pe.Graphics);
                }

ポイント6 マウスの状態によりイベントハンドラをオーバーライドする。

マウスがボタンの上に移動した場合に、ボタンの色を変更するようなイベントハンドラを追加します。
そんなことやらなくても☆型になるので、おまけ程度のものです。

ここでは、マウスがボタンの上に来たときに、OnMouseEnter イベントが発生するので、
isMouseOver を true にして、Invalidate() を呼ぶことにより、再描画をさせます。
Invalidate() を呼ぶと、OnPaint() が呼ばれることになります。
ここで、base.OnMouseEnter() で、ベースクラスをたたいてやる必要があります。
理由は、OnMouseXXXX によるベースクラスの内部状態が OnClick と完全に分離できていないためです。

なんで、そんなへんな依存関係があるんだ!?>>MS。
                protected override void OnMouseEnter(System.EventArgs e)
                {
                        base.OnMouseEnter(e);
                        this.isMouseOver = true;
                        Invalidate();           
                }

その他のOnMouseXXXXイベントも同様なので省略します。

5.Star Button のサンプルコード


StarButton サンプルのソースコード
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.ComponentModel;

namespace Uchukamen.Windows.Forms
{
        /// <summary>
        /// StarButton の概要の説明です。
        /// </summary>
        public class StarButton : System.Windows.Forms.Button
        {
                private bool isMouseOver = false;
                private bool isMouseDown = false;
                private Color borderColor = SystemColors.HotTrack;
                private Color mouseOvertColor = SystemColors.ActiveCaption;
                private Color mouseDownColor = Color.GreenYellow;

                public StarButton() : base() 
                {
                }

        #region Properties

                [Category( "Star Button" )]
                [Description( "境界の色" )]
                [DefaultValue( typeof(Color), "HotTrack" )]
                public Color BorderColor 
                {
                        get 
                        {
                                return this.borderColor;
                        }
                        set 
                        {
                                this.borderColor = value;
                                // cause a repaint if necessary
                                if (this.IsHandleCreated && this.Visible) 
                                {
                                        Invalidate();
                                }
                        }
                }
                [Category( "Star Button" )]
                [Description( "Mouse が上に来たときの色" )]
                [DefaultValue( typeof(Color), "ActiveCaption" )]
                public Color MouseOvertColor 
                {
                        get 
                        {
                                return this.mouseOvertColor;
                        }
                        set 
                        {
                                this.mouseOvertColor = value;
                                // cause a repaint if necessary
                                if (this.IsHandleCreated && this.Visible) 
                                {
                                        Invalidate();
                                }
                        }
                }
                [Category( "Star Button" )]
                [Description( "マウスが押されたときの色" )]
                [DefaultValue( typeof(Color), "GreenYellow" )]
                public Color MouseDownColor 
                {
                        get 
                        {
                                return this.mouseDownColor;
                        }
                        set 
                        {
                                this.mouseDownColor = value;
                                // cause a repaint if necessary
                                if (this.IsHandleCreated && this.Visible) 
                                {
                                        Invalidate();
                                }
                        }
                }
                #endregion

                protected void DrawButton(Graphics gr)
                {       
                        // ボタンの表示色を決定する。
                        Color currentColor;
                        currentColor = isMouseOver ? this.mouseOvertColor : this.BackColor;
                        currentColor = isMouseDown ? this.mouseDownColor : currentColor;

                        // Star の Path を計算する。
                        GraphicsPath starPath = this.GetPath();

                        // Star を描画する
                        Brush bgBrush = new SolidBrush( currentColor );
                        gr.FillPath(bgBrush, starPath);

                        // Star の境界線を描く
                        Pen pen = new Pen(BorderColor, 2.0F);
                        gr.DrawPath(pen, starPath);

                        // ボタンのリージョンを Star にする。
                        this.Region = new System.Drawing.Region(starPath);
                }

                /// <summary>
                /// 星型の Path を作る。
                /// </summary>
                /// <returns></returns>
                protected GraphicsPath GetPath()
                {
                        GraphicsPath gPath = new GraphicsPath();
                        int r = System.Math.Min(this.Width, this.Height)/2;

                        Point[] points = 
                                                {
                                                        new Point((int)(r*Math.Sin(Math.PI*2*0.0)), (int)(r*-Math.Cos(Math.PI*2*0))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*1/5)), (int)(r*-Math.Cos(Math.PI*2*1/5))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*2/5)), (int)(r*-Math.Cos(Math.PI*2*2/5))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*3/5)), (int)(r*-Math.Cos(Math.PI*2*3/5))),
                                                        new Point((int)(r*Math.Sin(Math.PI*2*4/5)), (int)(r*-Math.Cos(Math.PI*2*4/5))),
                        };

                        gPath.FillMode = System.Drawing.Drawing2D.FillMode.Winding;
                        gPath.AddLine(points[0], points[2]);
                        gPath.AddLine(points[2], points[4]);
                        gPath.AddLine(points[4], points[1]);
                        gPath.AddLine(points[1], points[3]);
                        gPath.AddLine(points[3], points[0]);

                        // 中心点を星の中心へ移す。
                        gPath.Transform(new System.Drawing.Drawing2D.Matrix(1,0,0,1,r,r));
                        return gPath;
                }

                protected override void OnPaint(PaintEventArgs pe) 
                {
                //      base.OnPaint(pe);
                        this.DrawButton(pe.Graphics);
                }
                protected override void OnMouseEnter(System.EventArgs e)
                {
                        base.OnMouseEnter(e);
                        this.isMouseOver = true;
                        Invalidate();           
                }

                protected override void OnMouseLeave(System.EventArgs e)
                {
                        base.OnMouseLeave(e);
                        this.isMouseOver = false;
                        Invalidate();
                }
                protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
                {
                        base.OnMouseUp(e);
                        this.isMouseDown = false;
                        Invalidate();
                }

                protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs mevent)
                {
                        base.OnMouseDown(mevent);
                        this.isMouseDown = true;
                        Invalidate();
                }
        }
}