An User Drawn Button

A flat button using vector graphics

This is a generic button control that uses vector graphics to generate the button surface for each of the possible states. Two type of button are emulated, push-on / push-off and momentary. The colours of each state are defined, with a hover state that allows a configurable tooltip. although the button can be used "as is" it is really meant to be the base control of a derived button.

A multistate button

The 6 states are enumerated in btnState and the button type enumerated in latch. Each of the states has a colour property. There is a tooltip property with definable text though normally a derived class would be used and the virtual hover text function overriden.

Progress Control

Progress Control

Progress Control

Progress Control

    [Description("FlatButton")]
    [ToolboxBitmap(typeof(Button))]
    [Designer(typeof(FlatButtonDesigner))]
    public partial class FlatButton : System.Windows.Forms.Control
        {
            private Color TransparentColour = Color.Transparent;
            private Color rest_colour = Color.White;
            private Color press_colour = Color.Pink;
            private Color latch_colour = Color.Green;
            private Color off_colour = Color.LightGray;
            private Color border_colour = Color.Black;
            private ToolTip button_tooltip = new ToolTip();
            private TextBox tooltip_text = new TextBox();
            private String sLatchedText = "Button Down";
            private String sRestText = "Button Up";
            private int Xsize = 48;
            private int Ysize = 48;
            //  Variables
            private bool bReady = false;
            private bool bLatched = false;
            private bool bHoverTip = false;
            latch latch_style = latch.Latch;

            public enum btnState
            {
                BUTTON_UP = 0,
                BUTTON_DOWN = 1,
                BUTTON_PRESS = 2,
                BUTTON_HOVER_RELEASE = 3,
                BUTTON_HOVER_LATCH = 4,
                BUTTON_GREY = 5,
            }

            public enum latch
            {
                Latch,
                Release
            }

            btnState imgState = btnState.BUTTON_UP;

            public FlatButton()
            {
                InitializeComponent();
                SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
                ButtonInitialise();
            }

            [Description("The button dimensions")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Size ButtonSize { get { return new Size(Xsize, Ysize); } set { Xsize = value.Width; Ysize = value.Height; this.Size = value; } }

            [Description("The button rest colour")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Color RestColour { get { return rest_colour; } set { rest_colour = value; ; } }

            [Description("The button latch colour")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Color LatchColour { get { return latch_colour; } set { latch_colour = value; ; } }

            [Description("The button press colour")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Color PressColour { get { return press_colour; } set { press_colour = value; ; } }

            [Description("The button off colour")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Color OffColour { get { return off_colour; } set { off_colour = value; ; } }

            [Description("The button border colour")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Color BorderColour { get { return border_colour; } set { border_colour = value; ; } }

            [Description("The latch or release press")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public latch LatchState { get { return latch_style; } set { latch_style = value; ; } }

            [Description("Display a Hover Tip")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public Boolean HoverTip { get { return bHoverTip; } set { bHoverTip = value; ; } }

            [Description("Hover text for button up")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public String HoverUpText { get { return sRestText; } set { sRestText = value; ; } }

            [Description("Hover text for button down")]
            [Category("FlatButton")]
            [RefreshProperties(RefreshProperties.All)]
            public String HoverDownText { get { return sLatchedText; } set { sLatchedText = value; ; } }

            private void ButtonInitialise()
            {
                this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnBtnDown);
                this.MouseHover += new System.EventHandler(this.OnBtnHover);
                this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.OnBtnUp);
                this.MouseLeave += new System.EventHandler(this.OnBtnLostHover);
                bReady = true;
                tooltip_text.Text = " ";
                button_tooltip.SetToolTip(tooltip_text, " ");
                Invalidate();
            }

            public void Hover(object sender, EventArgs e)
            {
                if (bReady == false) return;
                if (bLatched) imgState = btnState.BUTTON_HOVER_LATCH;
                else imgState = btnState.BUTTON_HOVER_RELEASE;
                Invalidate();
                if (bHoverTip) OnButtonHover(bLatched, sender, e, true);
            }

            public void OnButtonHover(Boolean bLatched, object sender, EventArgs e, Boolean bIsHovering)
            {
                if (bIsHovering)
                {
                    String sHoverText = "  ";
                    OnButtonHoverText(bLatched, sender, e, out sHoverText);
                    button_tooltip.Show(sHoverText, this);
                }
            }

            public virtual void OnButtonHoverText(Boolean bLatched, object sender, EventArgs e, out String sHoverText)
            {
                //  This is to be overriden
                if (bLatched) sHoverText = sLatchedText;
                else sHoverText = sRestText;
            }

            public void LostHover(object sender, EventArgs e)
            {
                if (bReady == false) return;
                if (bLatched) imgState = btnState.BUTTON_DOWN;
                else imgState = btnState.BUTTON_UP;
                Invalidate();
                if (bHoverTip) OnButtonHover(bLatched, sender, e, false);
            }

            public void Down()
            {
                if (bReady == false) return;
                imgState = btnState.BUTTON_PRESS;
                if ((latch_style == latch.Latch) && (bLatched == false)) bLatched = true;
                else bLatched = false;
                Invalidate();
                OnButtonDown();
            }

            public virtual void OnButtonDown() { } //  To be overriden

            public void Up()
            {
                if (bReady == false) return;
                if (bLatched) imgState = btnState.BUTTON_DOWN;
                else imgState = btnState.BUTTON_UP;
                Invalidate();
                OnButtonUp();
            }

            public virtual void OnButtonUp() { }   //  To be overriden

            private void OnBtnHover(object sender, EventArgs e) { this.Hover(sender, e); }
            private void OnBtnLostHover(object sender, EventArgs e) { this.LostHover(sender, e); }
            private void OnBtnDown(object sender, MouseEventArgs e) { this.Down(); }
            private void OnBtnUp(object sender, MouseEventArgs e) { this.Up(); }

            protected override void OnPaint(PaintEventArgs e)
            {
            Graphics gr = e.Graphics;
            Rectangle outer_space = new Rectangle(0, 0, (Xsize - 1), (Ysize - 1));
            Rectangle inner_space = new Rectangle(1, 1, (Xsize - 3), (Ysize - 3));
            Rectangle inner_hover = new Rectangle(1, 1, (Xsize - 2), (Ysize - 2));
            Pen border_pen = new Pen(border_colour, 3);
            //  Draw the border
            gr.DrawEllipse(border_pen, outer_space);
            border_pen.Dispose();
            switch (imgState)
                {
                case btnState.BUTTON_UP:
                    SolidBrush upBrush = new SolidBrush(rest_colour);
                    gr.FillEllipse(upBrush, inner_space);
                    upBrush.Dispose();
                    break;
                case btnState.BUTTON_DOWN:
                    SolidBrush downBrush = new SolidBrush(latch_colour);
                    gr.FillEllipse(downBrush, inner_space);
                    downBrush.Dispose();
                    break;
                case btnState.BUTTON_PRESS:
                    SolidBrush pressBrush = new SolidBrush(press_colour);
                    gr.FillEllipse(pressBrush, inner_space);
                    pressBrush.Dispose();
                    break;
                case btnState.BUTTON_HOVER_LATCH:
                    GraphicsPath latch_path = new GraphicsPath();
                    latch_path.AddEllipse(0, 0, inner_hover.Width, inner_hover.Height);
                    PathGradientBrush latchBrush = new PathGradientBrush(latch_path);
                    latchBrush.CenterColor = latch_colour;
                    Color[] boundary_colour = { rest_colour };
                    latchBrush.SurroundColors = boundary_colour;
                    latchBrush.CenterPoint = new Point(Xsize / 4, Ysize / 2);
                    gr.FillEllipse(latchBrush, inner_space);
                    latchBrush.Dispose();
                    break;
                case btnState.BUTTON_HOVER_RELEASE:
                    GraphicsPath release_path = new GraphicsPath();
                    release_path.AddEllipse(0, 0, inner_hover.Width, inner_hover.Height);
                    PathGradientBrush releaseBrush = new PathGradientBrush(release_path);
                    releaseBrush.CenterColor = rest_colour;
                    Color[] release_boundary_colour = { latch_colour };
                    releaseBrush.SurroundColors = release_boundary_colour;
                    releaseBrush.CenterPoint = new Point(((3 * Xsize) / 4), (Ysize / 2));
                    gr.FillEllipse(releaseBrush, inner_space);
                    releaseBrush.Dispose();
                    break;
                default:
                    SolidBrush greyBrush = new SolidBrush(off_colour);
                    gr.FillEllipse(greyBrush, inner_space);
                    greyBrush.Dispose();
                    break;
                }
            }

The heart of the button resides in the OnPaint override of the base control. here imagination is free to generate button of your wildest dreams. as can be seen the hover states use a gradient brush rather than a solid brush. If states are combined the "case" can be summed.

This button is a simple circle with a black border and a center that changes colour with the state assumed. If so desired there can be 6 different button shapes, something that only exists in cartoons and the meta-metal world.

        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }

The designer can be copied or the equivalent generated by hand by changing the properties of the designer form. The properties must coincide with that required by the .cs code. Note: only InitializeComponent() and declarations are here, the rest of the designer does not change!

        internal class FlatButtonDesigner : ControlDesigner
        {
            public FlatButtonDesigner()
            { }

            // clean up some unnecessary properties
            protected override void PostFilterProperties(IDictionary Properties)
            {
                    Properties.Remove("AllowDrop");
                    Properties.Remove("Size");
                    Properties.Remove("RightToLeft");
                    Properties.Remove("BackgroundImage");
                    Properties.Remove("BackgroundImageLayout");
                    Properties.Remove("ContextMenu");
                    Properties.Remove("Font");
                    Properties.Remove("MaximumSize");
                    Properties.Remove("MinimumSize");
                    Properties.Remove("Text");
                    Properties.Remove("TextAlign");
                    Properties.Remove("ForeColor");
                    Properties.Remove("ContextMenuStrip");
                    Properties.Remove("ImeMode");
                }
            }
            

A simple internal designer class override is used to remove non-required properties.