C# Round Button


Round buttons

Introduction

Some time ago, I tried to find a nice round-button control. I couldn’t find one, so in the time-honoured tradition, I decided to write my own. I “almost” finished it, but for various reasons, it was consigned to the “come back to it later” category. In its inimitable style, “later” finally arrived, and armed with my shiny new Microsoft Visual C# 2005 Express Edition, I decided to have a crack at finishing it.
Though I say it myself, I think the buttons look nice – you’ll have to judge for yourself! They look even better “in the flesh”, rather than as the JPEGs in this article.

Background

In my quest for a round button control, I came across a couple of articles (including one by the great man Chris Maunder himself!) which seemed to me and my Very Small Brain to have far too much complicated mathematics involved. Also, I’d been learning about graphics in C#, and doing a lot of experimenting with groovy things like the PathGradientBrush, taking much inspiration from the very excellent Bob Powell site. Probably, by chance, I forget exactly how, I stumbled upon the idea of layering ever decreasing circles filled with LinearGradientBrushes and PathGradientBrushes one above the other to build up a passable 3-D button. The following images illustrate this:
Hover your cursor for a description
The Button recess onlyThe Button recess with an edgeThe Button recess with an edge and an outer bevel
The Button recess with an edge and both bevelsThe Button recess with an edge, both bevels and a flat topThe Button recess with an edge, both bevels and a domed top

How it works

Well, putting lots of circles one on top of another is how it works, in essence. The control is derived from the Button class, and overrides the OnPaint method, where all the drawing is done. I’ve added some new properties:

  • RecessDepth – how far the button is set back into the containing surface.
  • BevelHeight – the size of the “outer” bevel of the button top.
  • BevelDepth – the size of the “inner” bevel.
  • Dome – whether or not the button has a “rounded” top.

These have all been added to the Button Appearance category of the Properties panel by decorating them with the appropriate attributes. In addition, I wrote a custom drop-down UITypeEditor for the RecessDepthproperty. I would never have managed this without Chris Sells’ excellent book Windows Forms Programming in C#, and I recommend it highly – I won’t attempt to explain how the UITypeEditor works, as it’s covered in a sample online chapter which discusses all aspects of Design Time IDE Integration (although I do own the actual book too!).
NB To get the ToolboxBitmap attribute to work properly, I had to add this dummy class, as suggested by that man Bob Powell, again in this article: ToolboxBitmap.

internal class resfinder
{
    // Trick from Bob Powell
}
.
.
.
.
[Description("Round (Elliptical) Button Control"),
    ToolboxBitmap(typeof(resfinder), "RoundButton.Images.RoundButton.bmp")]
    public class RoundButton : System.Windows.Forms.Button

Noteworthy sections of code

This is the overridden OnPaint method. Nothing too exciting, but I’ve included it for reference.

protected override void OnPaint(PaintEventArgs e)
{
    buttonColor = this.BackColor;
    edgeColor1 = ControlPaint.Light(buttonColor);
    edgeColor2 = ControlPaint.Dark(buttonColor);
    Graphics g = e.Graphics;
    g.SmoothingMode = SmoothingMode.AntiAlias;
    Rectangle buttonRect = this.ClientRectangle;
    edgeWidth = GetEdgeWidth(buttonRect);
    FillBackground(g, buttonRect);
    if (RecessDepth > 0)
    {
        DrawRecess(ref g, ref buttonRect);
    }
    DrawEdges(g, ref buttonRect);
    ShrinkShape(ref g, ref buttonRect, edgeWidth);
    DrawButton(g, buttonRect);
    DrawText(g, buttonRect);
    SetClickableRegion();
}

Next is the DrawRecess method, which creates the illusion of the button being set into the form surface. The Blend object allows you to specify at which points across the rectangle, and by how much, the two colours in the LinearGradientBrush are blended. I arrived at these parameters by trial and error until it looked right to me, so they are purely subjective. ControlPaint.Dark and ControlPaint.Light are extremely useful here, because they create lighter and darker shades of the parent background colour. This, of course, assumes that the illusion we want to create is that of a form made of a solid piece of coloured material, rather than one which is still grey but has been painted a different colour. If that’s what you’d prefer, then simply change Parent.BackColor to Color.FromKnownColor(KnownColor.Control).
The interesting thing I found here was the “Using this second smaller rectangle…” part. I use the same technique again in the BuildGraphicsPath method, and although it creates much smoother curves, I have no idea how or why it actually works. But then, how many of us really know how TV works…?

protected virtual void DrawRecess(ref Graphics g, ref Rectangle recessRect)
{
    LinearGradientBrush recessBrush = new LinearGradientBrush(recessRect,
                                      ControlPaint.Dark(Parent.BackColor),
                                      ControlPaint.LightLight(Parent.BackColor),
                                      GetLightAngle(Angle.Up));
    // Blend colours for realism
    Blend recessBlend = new Blend();
    recessBlend.Positions = new float[] {0.0f,.2f,.4f,.6f,.8f,1.0f};
    recessBlend.Factors = new float[] {.2f,.2f,.4f,.4f,1f,1f};
    recessBrush.Blend = recessBlend;
    // Using this second smaller rectangle
    // smooths the edges - don't know why...?
    Rectangle rect2 = recessRect;
    ShrinkShape(ref g, ref rect2, 1);
    FillShape(g, recessBrush, rect2);
    ShrinkShape(ref g, ref recessRect, recessDepth); //orig
}

You’ll notice a copious amount of ShrinkShape(ref g, ref edgeRect, 1); statements in the source. This is the method by which the “ever decreasing circles” are created. I use a ref parameter so that the rectangle in question just keeps getting smaller.
To draw the domed top, I simply use this bit of code in the DrawButton method. The default value for cColor is White, so if we want a domed top, we set the CenterColor to white, and calculate a CenterPoint based on the size of the button.

pgb.CenterColor = buttonColor;
if (dome)
{
    pgb.CenterColor = cColor;
    pgb.CenterPoint =
        new PointF(buttonRect.X + buttonRect.Width / 8 + buttonPressOffset,
                   buttonRect.Y + buttonRect.Height / 8 + buttonPressOffset);
}
FillShape(g, pgb, buttonRect);

Drawing the text on the button is accomplished with the DrawText method, shown below. It uses the Font and ForeColor properties inherited from the base Button class. I used my VerticalString class to write upright text if the button’s height is more than twice its width. VerticalString was the subject of a previous CodeProject article here, and I’ve included the source in the project download for completeness. I also had to ensure that where possible, the button text stays within the bounds of the button. As part of this process, I had to convert the alignment of the text from ContentAlignment to StringAlignment. Finally, I check to see if the button is disabled, and if so, I “grey out” the text.

protected void DrawText(Graphics g, Rectangle textRect)
{
    labelStrFmt = new StringFormat();
    labelBrush = new SolidBrush(this.ForeColor);
    labelFont = this.Font;    // Get the caller-specified font
    vs = new VerticalString();
    vs.TextSpread = .75;
    // Check for tall button, and write text vertically if necessary
    bool verticalText = false;
    if (textRect.Height > textRect.Width * 2)
    {
        verticalText = true;
    }
    // Convert the text alignment from
    // ContentAlignment to StringAlignment
    labelStrFmt.Alignment = ConvertToHorAlign(this.TextAlign);
    labelStrFmt.LineAlignment = ConvertToVertAlign(this.TextAlign);
    // If horizontal text is not horizontally centred,
    // or vertical text is not vertically centred,
    // shrink the rectangle so that the text doesn't stray outside the ellipse
    if ((!verticalText & (labelStrFmt.LineAlignment != StringAlignment.Center)) |
        (verticalText & (labelStrFmt.Alignment != StringAlignment.Center)))
    {
        textRect.Inflate(-(int)(textRect.Width/7.5),
                         -(int)(textRect.Height/7.5));
    }
    textRect.Offset(buttonPressOffset, buttonPressOffset);
    // Apply the offset if we've been clicked
    // If button is not enabled, "grey out" the text.
    if (!this.Enabled)
    {
        //Write the white "embossing effect" text at an offset
        textRect.Offset(1, 1);
        labelBrush.Color = ControlPaint.LightLight(buttonColor);
        WriteString(verticalText, g, textRect);
        //Restore original text pos, and set text colour to grey.
        textRect.Offset(-1, -1);
        labelBrush.Color = Color.Gray;
    }
    //Write the text
    WriteString(verticalText, g, textRect);
}

The illusion of the button being pressed is achieved in the two small methods below. When the user presses the button, the buttonPressOffset variable is set to 1, and the virtual light angle is altered so that the top left of the button becomes dark, and the bottom right becomes light, creating the impression that the button has receded into the form surface. When the button is released, the values revert to normal.

protected void buttonDown()
{
    lightAngle = Angle.Down;
    buttonPressOffset = 1;
    this.Invalidate();
}
protected void buttonUp()
{
    lightAngle = Angle.Up;
    buttonPressOffset = 0;
    this.Invalidate();
}

Finally, a couple of points…

The RoundButton control only supports FlatStyle.Standard. I wrote some code for FlatStyle.Flat and FlatStyle.Popup, which worked OK, but I wasn’t completely happy with either the code or the results, so I took it out.
If you look at the source, you might notice a Region called Overrideable shape-specific methods, containing uninspiring methods like these:

protected virtual void AddShape(GraphicsPath gpath, Rectangle rect)
{
    gpath.AddEllipse(rect);
}
protected virtual void DrawShape(Graphics g, Pen pen, Rectangle rect)
{
    g.DrawEllipse(pen, rect);
}

“C# Round Button”의 66개의 댓글

  1. Greetings from Los angeles! I’m bored to death at work so I decided
    to check out your blog on my iphone during lunch break.
    I love the information you present here and can’t wait to
    take a look when I get home. I’m surprised at how fast your blog loaded on my phone ..
    I’m not even using WIFI, just 3G .. Anyhow, great blog!

  2. Its like you learn my mind! You appear to know a lot approximately this, like you wrote the book in it or something.
    I feel that you just can do with a few percent to drive the
    message home a little bit, but other than that, this is fantastic
    blog. A fantastic read. I’ll definitely be back.

  3. I simply want to mention I’m new to blogging and site-building and truly enjoyed your page. Very likely I’m planning to bookmark your blog post . You certainly have excellent article content. Regards for revealing your web page.

  4. 핑백: http://pressat.net/

  5. 핑백: سایت و لینک بدون فیلتر ریور پوکر

  6. 핑백: w88 lite

  7. 핑백: nằm mơ thấy con rết là điềm gì

  8. 핑백: mơ thấy ao nước đánh con gì

  9. 핑백: mơ mình chết đánh con gì

  10. 핑백: Pomsky puppies for sale near me

  11. 핑백: f1b labradoodles for sale

  12. 핑백: Magazines for Sale online

  13. 핑백: interqq

  14. 핑백: mo thay vang

  15. 핑백: idrpoker

  16. I just want to tell you that I am just newbie to blogs and honestly savored your page. Likely I’m want to bookmark your website . You surely have exceptional posts. With thanks for sharing with us your website.

  17. 핑백: mơ quả mít đánh con gì

  18. 핑백: mơ bị đòi nợ

  19. I have learned a few important things as a result of your post. I might also like to state that there might be situation that you will get a loan and do not need a cosigner such as a National Student Support Loan. When you are getting that loan through a regular loan provider then you need to be able to have a co-signer ready to assist you. The lenders may base any decision over a few variables but the most important will be your credit history. There are some loan merchants that will as well look at your job history and make a decision based on this but in many cases it will hinge on your ranking.

  20. 핑백: r6s aimbot

  21. I’m really impressed with your writing skills as well as with the layout on your blog. Is this a paid theme or did you modify it yourself? Anyway keep up the nice quality writing, it is rare to see a great blog like this one these days..

  22. I just want to mention I’m newbie to weblog and actually enjoyed this blog site. Likely I’m going to bookmark your blog post . You absolutely have tremendous posts. With thanks for sharing with us your webpage.

  23. Does your website have a contact page? I’m having trouble locating it but, I’d like to send you an email. I’ve got some suggestions for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it grow over time.

  24. Howdy! I could have sworn I’ve been to this blog before but after browsing through some oof tthe post I realized it’s new to me.
    Nonetheless, I’m definitely delighted I found it and I’ll be bookmarking and checking
    bak frequently!

  25. I have been exploring for a little bit for any high quality articles or blog posts on this kind of area . Exploring in Yahoo I at last stumbled upon this web site. Reading this information So i’m happy to convey that I’ve a very good uncanny feeling I discovered exactly what I needed. I most certainly will make sure to do not forget this site and give it a glance on a constant basis.

  26. I am commenting to let you be aware of of the outstanding discovery my wife’s child found visiting your webblog. She realized plenty of pieces, which included what it’s like to have an incredible teaching spirit to let other people without problems know precisely specific multifaceted things. You actually exceeded her desires. Thank you for churning out the powerful, dependable, edifying and even easy tips about your topic to Sandra.

  27. Hey very nice site!! Guy .. Excellent .. Superb .. I’ll bookmark your website and take the feeds also…I am happy to seek out so many helpful info right here in the publish, we want work out more strategies on this regard, thanks for sharing.

  28. I’m extremely impressed with your writing skills as well as with the layout on your weblog. Is this a paid theme or did you modify it yourself? Anyway keep up the excellent quality writing, it’s rare to see a great blog like this one today..

  29. There are a handful of fascinating points with time in this post but I don’t know if these center to heart. You can find some validity but I’ll take hold opinion until I explore it further. Good write-up , thanks and that we want far more! Included with FeedBurner as well

  30. I simply want to mention I’m newbie to blogs and truly loved this blog. Very likely I’m going to bookmark your website . You surely come with very good stories. Thanks a bunch for revealing your blog.

  31. What i don’t understood is in reality how you’re no longer really a lot more well-appreciated than you may be right now. You are so intelligent. You already know thus significantly on the subject of this subject, produced me in my view consider it from a lot of various angles. Its like men and women are not interested until it is something to do with Woman gaga! Your individual stuffs nice. At all times handle it up!

  32. 핑백: mơ thấy quả chuối

댓글 남기기