Introduction
I wanted to be able to create non-rectangular buttons, just out of interest more than anything else. I had seen several complex versions on the web, but I thought I could come up with something simpler. Whilst trawling the web, I came across a flash version of Simon, which I enjoyed playing, so here is a recreation of it with my shaped buttons.
There is no sound, but if someone wants to add it, then please send me a copy of the updated code.
Shaped Button
This was achieved using the following steps:
- Add a new
UserControl
to your application. - View the code and change the inherited type from
UserControl
to Button
.
public partial class simonButton : Button
{
public simonButton()
{
InitializeComponent();
}
}
- The shape of the button is achieved through the use of two
GraphicsPath
objects. Declare these in the top of the button class.
public partial class simonButton : Button
{
private GraphicsPath path;
private GraphicsPath innerPath;
...
The path
denotes the outline of the button, while the innerPath
denotes the shape of the flat surface of the button. - Now override the
OnPaint
method of the button, to take care of the drawing, so that is looks like the following code.
protected override void OnPaint(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
}
So that we do not get jagged curves, set the SmoothingMode
to AntiAlias
. - The
path
object is filled with a LinearGradientBrush
. This is simple to achieve by supplying a rectangle and a new LinearGradientBrush
.
Rectangle rect = new Rectangle(0, 0, 150, 150);
LinearGradientBrush linearBrush =
new LinearGradientBrush(rect,
Color.FromArgb(40,40,40),
this.ForeColor,
225);
Next we instantiate the path
object.
path = new GraphicsPath();
Add items to the path
object to create the required shape.
path.AddArc(0, 0, 270, 270, 180, 90);
path.AddArc(120, 0, 30, 30, 270, 90);
path.AddLine(150, 0, 150, 85);
path.AddArc(100, 100, 100, 100, -90, -90);
path.AddLine(100, 150, 0, 150);
path.AddArc(0, 120, 30, 30, 90, 90);
path.AddArc(0, 0, 270, 270, 180, 90);
Fill the path.
g.FillPath(linearBrush, path);
Finally, dispose of the brush to free memory.
linearBrush.Dispose();
- The
innerPath
object is filled with a SolidBrush
.
Brush b = new SolidBrush(this.ForeColor);
Next we instantiate the innerPath
object.
innerPath = new GraphicsPath();
Add items to the innerPath
object to create the required shape.
innerPath.AddArc(10, 10, 250, 250, 180, 90);
innerPath.AddArc(130, 10, 10, 10, 270, 90);
innerPath.AddLine(140, 0, 140, 90);
innerPath.AddArc(90, 90, 100, 100, -90, -90);
innerPath.AddLine(90, 140, 10, 140);
innerPath.AddArc(10, 130, 10, 10, 90, 90);
Fill the innerPath.
g.FillPath(b, innerPath);
Finally, dispose of the brush to free memory.
b.Dispose();
- So far, this draws our shaped button, but you can click anywhere in the associated rectangle, so to stop this, simply set the
Region
property of the button to path
.
this.Region = new Region(path);
The button will now only accept clicks within the region of the shape. - So that the user has some feedback that they are over a button, changing the cursor is a simple thing to do. Override the
OnMouseEnter
and OnMouseLeave
methods and change the cursor within them.
protected override void OnMouseEnter(EventArgs e)
{
this.Cursor = Cursors.Hand;
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
this.Cursor = Cursors.Arrow;
base.OnMouseLeave(e);
}
- The button also needs visual feedback that it has been pressed. In this case, the button wants to appear as if a light has come on behind it. This requires several new items and some changes to the code. First off, a boolean value is required to store if the click has happened so that it can be drawn in the correct state.
private bool _clicked = false;
public bool Clicked
{
get { return _clicked; }
set
{
_clicked = value;
Invalidate();
}
}
Now override the OnMouseDown
and OnMouseUp
to set the Clicked
state accordingly.
protected override void OnMouseDown(MouseEventArgs mevent)
{
_clicked = true;
base.OnMouseDown(mevent);
}
protected override void OnMouseUp(MouseEventArgs mevent)
{
_clicked = false;
base.OnMouseUp(mevent);
}
Add a new brush.
PathGradientBrush pgbrush = new PathGradientBrush(innerPath);
pgbrush.CenterPoint = new Point(75, 75);
pgbrush.CenterColor = Color.White;
pgbrush.SurroundColors = new Color[] { this.ForeColor };
Redraw the button according to the Clicked
state.
if (_clicked == false)
{
g.FillPath(linearBrush, path);
g.FillPath(b, innerPath);
}
else
{
g.FillPath(linearBrush, path);
g.FillPath(pgbrush, innerPath);
}
Remember to dispose of the brush correctly.
Using the code
Once you have built the application, just drop the control on a form. The buttons are a fixed size, so they are not scaleable. Again, if someone wants to improve on this please send me a copy of the code.
Game Logic
The game logic is fairly simple, so I will not write about that here, except to say that only sequences of 1000 are generated. My best score so far is 18, so I think 1000 is reasonable.
Have fun.
History
- May 11, 2007 - Original version posted