I have been away for a while, enjoying some of the theater in the London West End. I learned quite a few things on the trip. First, as a visit to the Duke of York's Theater proved, sitting and listening to Jeremy Irons (in Embers) is an extremely good use of your time. Second, as a visit to the Old Vic theater proved, if you see Kevin Spacey speaking with a distinguished looking gentleman at the end of your row (at Resurrection Blues), and you ask a bit too loudly what Robert Altman looks like, he will respond, "he looks like me."

To get back into the swing of things, I have decided to veer away a bit from some of the more theoretical discussions and delve into some practical issues of creating great looking applications. There is a lot of excitement out there around some of the interesting new technologies looming on the horizon. Windows Presentation Foundation (formerly Avalon) provides you with a sack full of goodies for creating some extremely compelling user interfaces. However, that doesn't mean that you can't use the tools available today to create some compelling visualizations right away! One frequently overlooked gem is the technology already available with Windows Forms, which provides you with a very robust platform for creating fantastic visualizations.

To begin this exploration, I have selected the ever popular Gel Buttons. Valentin Iliescu, a Visual C# MVP, recently posted a blog entry on how to create Gel Buttons using WPF and the Expression Interactive Designer. We can do the same thing with Windows Forms.

Windows Forms Gel Buttons : First Revision

If we look at the overall problem, what we want to create is fairly straightforward. the bottom layer of the button is a smooth gradient, from some starting color to some ending color. Next, we layer on top of that a gradient starting with completely opaque white and ending with completely transparent white, which creates the highlight. Finally, we can draw the text. Since we are looking to implement all of the behaviors of a button, and modify only the appearance, we can inherit from Button and override the drawing behavior. We will want to add a couple of additional properties to allow our users to customize the gradient colors. For this first installment, that's all that we have to do! Let's take a look at the code to implement this.

namespace GelButtons {

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

class GelButton : Button {

Color gradientTop = Color.FromArgb(255, 44, 85, 177);
Color gradientBottom = Color.FromArgb(255, 153, 198, 241);

[Category("Appearance"), Description("The color to use for the top portion of the gradient fill of the component.")]
public Color GradientTop {
get {
return this.gradientTop;
}
set {
this.gradientTop = value;
this.Invalidate();
}
}

[Category("Appearance"), Description("The color to use for the bottom portion of the gradient fill of the component.")]
public Color GradientBottom {
get {
return this.gradientBottom;
}
set {
this.gradientBottom = value;
this.Invalidate();
}
}

protected override void OnPaint(PaintEventArgs pevent) {
Graphics g = pevent.Graphics;
// Fill the background
using (SolidBrush backgroundBrush = new SolidBrush(this.BackColor)) {
g.FillRectangle(backgroundBrush, this.ClientRectangle);
}
// Paint the outer rounded rectangle
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle outerRect = new Rectangle(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
using (GraphicsPath outerPath = RoundedRectangle(outerRect, 5, 0)) {
using (LinearGradientBrush outerBrush = new LinearGradientBrush(outerRect, gradientTop, gradientBottom, LinearGradientMode.Vertical)) {
g.FillPath(outerBrush, outerPath);
}
using (Pen outlinePen = new Pen(gradientTop)) {
g.DrawPath(outlinePen, outerPath);
}
}
// Paint the highlight rounded rectangle
Rectangle innerRect = new Rectangle(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - 1, ClientRectangle.Height / 2 - 1);
using (GraphicsPath innerPath = RoundedRectangle(innerRect, 5, 2)) {
using (LinearGradientBrush innerBrush = new LinearGradientBrush(innerRect, Color.FromArgb(255, Color.White), Color.FromArgb(0, Color.White), LinearGradientMode.Vertical)) {
g.FillPath(innerBrush, innerPath);
}
}
// Paint the text
TextRenderer.DrawText(g, this.Text, this.Font, outerRect, this.ForeColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
}

private GraphicsPath RoundedRectangle(Rectangle boundingRect, int cornerRadius, int margin) {
GraphicsPath roundedRect = new GraphicsPath();
roundedRect.AddArc(boundingRect.X + margin, boundingRect.Y + margin, cornerRadius * 2, cornerRadius * 2, 180, 90);
roundedRect.AddArc(boundingRect.X + boundingRect.Width - margin - cornerRadius * 2, boundingRect.Y + margin, cornerRadius * 2, cornerRadius * 2, 270, 90);
roundedRect.AddArc(boundingRect.X + boundingRect.Width - margin - cornerRadius * 2, boundingRect.Y + boundingRect.Height - margin - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 0, 90);
roundedRect.AddArc(boundingRect.X + margin, boundingRect.Y + boundingRect.Height - margin - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 90, 90);
roundedRect.CloseFigure();
return roundedRect;
}

}
}

Now that we have explored the fact that we can create a gel button, we should consider when we should create a gel button. Common controls exist for a reason - they provide a consistent user interface, allowing every application to feel familiar to the user. When you rely on common controls, particularly when you style these controls using visual styles, you get some degree of assurance that your controls will be updated with subsequent versions of the operating system, and that they will change when the user changes the visual style they are using to look consistent with other screen elements.

However, not all applications necessarily want to play by these rules. Consider Windows Media Player - the buttons here are not just simple button controls, but they are highly stylized controls that are appropriate to the overall feel of the application. If you are developing something that is more consumer targeted, or that has a specific UI theme around which you are building the entire user experience, then perhaps you don't want to be limited to what the operating system provides. Doing some custom painting is fairly straightforward, and gives you the ability to finely craft your presentation to suit the experience you are hoping to deliver.

Thinking about custom painting, things become even more interesting when you consider creating a type of control that the operating system does not provide. It is probably better to implement a control specifically targeted to support your application than to bend the user experience to fit into the constraints of the common controls that are already provided. We will delve a bit into this in later episodes.

Having considered that, let's consider how complete we feel this implementation is. Here, we are rendering a static image. Depending on your aesthetic tastes, you may feel that these look nice, but in essence they are nothing but static images. We could achieve static images using an image control alone, so why spend the time custom drawing it? After all, it is faster just to move memory around than it is to perform computations in order to complete our rendering. This is where Windows Forms really hits its sweet spot - having complete control over the video card gives you limitless possibilities for rich interaction. We don't need to stop at rendering a picture and calling it a button (which is very frequent on the web). Rather, we would like our button to be pliant. We don't want it to just look like a button, we would like it to behave like a button. We want it to announce clearly that it is clickable. When we move the mouse over the button, we would like it to tell us that we have moved over an object that will respond to us. When we push the button, we would like for it to look pushed. So, in our next episode, let's do exactly that - let's harness the capabilities that Windows Forms provides to make a gel button that actually behaves like a button, rather than just using our code to draw an interesting looking picture.