ControlFreak
LANGUAGES:
C# | VB.NET
ASP.NET
VERSIONS: 1.x
Beautiful Buttons Abound
Create Attractive Buttons with the NiftyButton Control
By Steve C. Orr
The standard Web button control is boring, pedestrian, and
ugly. That s why few attractive Web sites use them. Instead, most use images. The
drawback is that it takes a significant amount of time to create enough images
to fill all the image buttons of an entire Web site. It also takes time to
manage them and redo them all the next time the design of the site changes. Wouldn t
it be nice to have a better button control that could radically change its own
appearance just by setting a few properties? Well now you ll have one.
The NiftyButton control we ll create in this article can
be used to create strikingly attractive buttons that can be created and
configured dynamically, with no images required. This control builds on my
DirectX column Eye
Candy by utilizing filters to do the heavy lifting.
To create such a control, you might first envision a
control that generates its own images by using the System.Drawing namespace; for
more on the use of the System.Drawing namespace, see Improve
Your Image(s). This could potentially save some image management effort and
create attractive results. However, Internet Explorer dictates that images must
ultimately be generated via their own URL, which means that any kind of image
button must rely on code that doesn t exist in the current page. Therefore, you
must have another page to generate the image or an HTTP handler to intercept
such requests and do the work. This makes deployment and configuration a
hassle. Wouldn t it be better to have a button control that can change its
appearance without relying on images? Well, now you ll have one.
Figure 1: This button was created
entirely with HTML. No images were required. The results look great in Internet
Explorer, and the special effects are ignored by other browsers (which end up
displaying fairly standard looking buttons).
If you recall from Eye
Candy, filters can be applied to standard buttons to improve their look. The
following snippet transforms an ugly gray square button into the fuzzy blue
oval button shown in Figure 1:
<input type="submit"
name="NiftyButton10"
value="Oval Shaped" id="NiftyButton10"
style="FILTER:
progid:DXImageTransform.Microsoft.Alpha(style=2,startX=0,
startY=0,finishX=100,finishY=100,opacity=100,
finishOpacity=0);
COLOR:deepskyblue;BACKGROUND-COLOR:darkblue">
The Alpha filter defined inside the style for this button
accepts up to seven parameters. In addition, you can combine multiple filters
for a seemingly infinite number of effects. You ve probably noticed this HTML
definition is rather nonstandard looking. It s an odd way to define parameters
and the resulting HTML can get quite complex when combining multiple filters. It
can also end up being quite a maintenance chore to keep all of these
complicated definitions consistent across a large Web site. As if that weren t
enough complexity, Internet Explorer is very picky about line breaks and spaces
in filter definitions, and Visual Studio 2003 will happily (and often) reformat
the HTML so that it changes just enough to break the code, but not enough to
make the cause of the problem obvious. Wouldn t it be nice to have a button
control that encapsulated all this complexity by exposing valid filter options
and parameters through a few well designed properties?
A Nifty Button Control
Like all custom controls, the NiftyButton control is a DLL
that you can add to your Visual Studio toolbox (by right clicking on the
toolbox, choosing Add/Remove Items and browsing to the DLL). Then you can
drag the control onto any Web form and set its properties to change how it will
look at run time.
Using the NiftyButton control, a button identical to that
shown in Figure 1 can be created with the following definition:
<cc1:NiftyButton id="NiftyButton10"
runat="server"
Text="Oval Shaped"
BackColor="DarkBlue" Alpha-Visible="True"
Alpha-StartX="0" Alpha-StartY="0"
Alpha-Style="Radial"
Alpha-FinishOpacity="0"
Alpha-FinishY="100"
Alpha-FinishX="100" Alpha-Opacity="100"
ForeColor="DeepSkyBlue"></cc1:NiftyButton>
This HTML snippet looks much cleaner and more intuitive
than the previous example. Of course, you need not muck with such definitions
at all because the designer and properties window in Visual Studio can manage
all of this for you.
The NiftyButton control inherits from the standard button Web
control, so it gets all of that functionality automatically. The NiftyButton
control adds properties to manage parameters for the nine different filters
that it supports. It then concatenates the required style strings and outputs
them via the OnPreRender event listed in Figure 2.
protected override void OnPreRender(EventArgs e)
{
//remove the old
filter string (if any)
this.Style.Remove("filter");
//Build the new
filter style string
StringBuilder sb =
new StringBuilder();
if
(this.FlipHorizontal) sb.Append("fliph ");
if
(this.FlipVertical) sb.Append("flipv ");
if (this.XRay)
sb.Append("xray ");
// delegate the
subclasses to add their
// own filter style
strings
sb.Append(_Alpha.GetStyle());
sb.Append(_Blur.GetStyle());
sb.Append(_DropShadow.GetStyle());
sb.Append(_Emboss.GetStyle());
sb.Append(_Engrave.GetStyle());
sb.Append(_Glow.GetStyle());
//Output the
completed style string to the control
if (sb.Length>0)
this.Style.Add("filter",
System.Environment.NewLine
+ sb.ToString());
}
Figure 2: The
OnPreRender event of the NiftyButton is where the majority of the control s
functionality is orchestrated.
First, any preexisting filters (that may have been placed
there on a previous postback) are removed so that the current settings will be
rendered instead.
In the second code block, a few of the more simple
(paramaterless) filters are generated and added to the StringBuilder buffer. The
third code block calls the GetStyle method implemented in the subclasses that
represent the more complex filters. Each of these subclasses will generate
their own output and add it to the StringBuilder buffer. These subclasses will
be examined in more detail later in this article.
The final line in Figure 2 flushes the StringBuilder
buffer into the page by adding the resulting string to the Style collection of
the control.
X-Ray Vision
XRay is a simple filter that takes no parameters. When
enabled, it averages the red and green values to create a grayscale output that
can look rather alluring, especially when combined with other filters. This
filter is exposed through a simple Boolean property of the NiftyButton control:
[Bindable(true), Category("Filter"),
Description("Grayscale, averages red & green
values.")]
public bool XRay
{
get
{
return((ViewState["xray"]
== null) ? false :
(bool)ViewState["xray"]);
}
set {ViewState["xray"] = value;}
}
The property has three attributes. The first attribute (Bindable)
permits data binding. The second attribute (Category) ensures this property
will appear under the Filter category of the properties window when the
control is compiled and placed on a Web form. The third attribute (Description)
will appear in the bottom portion of the properties window when the property is
selected. The get and set methods use ViewState to store the property value so
it will be persisted between page postbacks. If no value is found in ViewState,
the default of False will be used, which will turn off the XRay effect. The
NiftyButton control contains several other similar properties that can be
examined by downloading the source code (see end of article for details). See
Figure 3 for a partial list of filters managed by the NiftyButton control.
|
Filter
|
Description
|
Example Syntax
|
|
Alpha
|
Adjusts the opacity of the object.
|
progid:DXImageTransform.Microsoft.Alpha(style=2,
startX=0, startY=0, finishX=100, finishY=100, opacity=100, finishOpacity=0)
|
|
DropShadow
|
Creates a solid silhouette of the object, offset in the
specified direction.
|
progid:DXImageTransform.Microsoft.DropShadow(color=red,
offX=2, offY=2, positive=true)
|
|
Emboss
|
Displays the object as embossed texture with grayscale.
|
progid:DXImageTransform.Microsoft.Emboss(Bias=50)
|
|
Engrave
|
Displays the object as engraved texture with grayscale.
|
progid:DXImageTransform.Microsoft.Engrave(Bias=50)
|
|
Glow
|
Adds radiance around the edges of the object.
|
progid:DXImageTransform.Microsoft.Glow(color=red,
strength=6)
|
|
MotionBlur
|
Causes the object to appear to be in motion.
|
progid:DXImageTransform.Microsoft.MotionBlur(add=true,
direction=180, strength=6)
|
Figure 3: These
are a few of Microsoft s many DirectX filters that are exposed for Web page
elements to consume. These filters (and others) are managed automatically by
the NiftyButton control so you needn t be worried about the somewhat arcane
syntax.
A Glowing Review
The Glow filter is more complex than the XRay filter. The
Glow filter accepts several parameters, which are exposed via NiftyButton
properties. The Glow property is read-only, and is publicly exposed with the
following code:
[DesignerSerializationVisibility
(DesignerSerializationVisibility.Content),
NotifyParentProperty(true), Category("Filter")]
public glow Glow
{
get{return _Glow;}
}
The get method simply returns an instance of the Glow
class, which is encapsulated within the NiftyButton class. More interesting in
this code snippet, however, are the attributes. As mentioned previously, the
Category attribute specifies that this property will show up in the Filter
section of the property window. The other two attributes ensure that this
subclass will persist its properties. The NotifyParentProperty attribute
ensures that any changes to the properties in this subclass will bubble up to
the containing NiftyButton object so that they will manage state in unison. The
DesignerSerializationVisibility attribute ensures that changes made in the
properties window of Visual Studio will be persisted to the HTML definition in
the ASPX page.
The source code for the Glow class is shown in Listing One. The Glow class is a subclass of the
NiftyButton control. It encapsulates the complexities involved with managing
and displaying the Glow filter. The class is defined using the TypeConverter
attribute, which is given the ExpandableObjectConverter as a parameter. This
ensures that the properties of this class will appear in the properties window
in an expandable way. That is, a + sign will appear next to the property to
let it expand and show all its properties at design time, as shown in Figure 4.
Figure 4: The
ExpandableObjectConverter TypeConverter attribute allows subclasses to be
expanded to show their properties. In this example, the Glow class has been
expanded (via the plus signs along the left) to show its three properties:
Color, Strength, and Visible.
The constructor of the Glow class shown in Listing One
accepts a StateBag as a parameter, so it can accept ViewState as a parameter
from the containing NiftyButton control. The Visible property (which is False
by default) uses ViewState to retain its value between page postbacks. The
Color property is similar, with a default value of Red. The Strength property
adds some basic bounds checking to ensure the value is not zero.
The ToString property is overridden so a value can be
displayed directly next to the Glow property in the properties window (as illustrated
in Figure 4). In this case, it merely displays Visible or Not Visible , but it
could easily display a more complex representation of the data contained within
the object.
The final piece of code in the Glow class is the GetStyle
function. It concatenates the Glow filter s Style string together according to
the property values. It inserts spaces and new line characters where necessary
to ensure proper display in the browser.
The Glow class is one of many subclasses in the
NiftyButton control. The rest of the subclasses have the same basic design and
can be downloaded for a detailed review.
More Niftiness?
Standard buttons are boring, Image buttons are a
maintenance hassle, and filters (by themselves) use an overly complicated
syntax. I hope you agree that the NiftyButton control is a nifty solution for
all these problems (see Figure 5).
Figure 5: The NiftyButton control
can display in a nearly infinite number of ways by simply adjusting some
properties.
After examining the NiftyButton source code you should
have a good understanding of how to build custom controls, inherit and extend
existing Web controls, and use filters to visually enhance controls. You should
also have a good feel for creating controls that provide a quality design-time
experience by using attributes to expose complex public property trees.
Give the NiftyButton a whirl and let me know what you
think of it. Feel free to tinker with the code, expand upon it, and learn from
it. Drop me an e-mail if you come across any interesting new revelations on the
subject; I d love to hear about them.
References
Filter References
http://www.w3schools.com/dhtml/dhtml_css.asp
http://msdn.microsoft.com/workshop/author/filter/reference/reference.asp
ExpandableObjectConverter
http://www.bluevisionsoftware.com/WebSite/TipsAndTricksDetails.aspx?Name=ExpandableObjectConverter
The sample code in
this article is available for download.
Steve C. Orr is an
MCSD and a Microsoft MVP in ASP.NET. He s been developing software solutions
for leading companies in the Seattle
area for more than a decade. When he s not busy designing software systems or
writing about them, he can often be found loitering at local user groups and
habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net
or e-mail him at mailto:Steve@Orr.net.
Begin Listing One
[TypeConverter(typeof(ExpandableObjectConverter))
Description("Adds radiance around the button
edges.")]
public class glow {
private StateBag _ViewState;
public glow(StateBag ViewState) //Constructor
{
_ViewState=ViewState;
}
[NotifyParentProperty(true),
Description("Should the glow be visible at
runtime?")]
public bool Visible
{
get
{
return((_ViewState["glowVisible"]
== null) ? false :
(bool)_ViewState["glowVisible"]);
}
set {_ViewState["glowVisible"] =
value;}
}
[NotifyParentProperty(true),
Description("The color of the glow around the
button.")]
public Color Color
{
get
{
return((_ViewState["GlowColor"]
== null) ? Color.Red :
(Color)_ViewState["GlowColor"]);
}
set
{_ViewState["GlowColor"]
= value;}
}
[NotifyParentProperty(true),
Description("Number of pixels that the effect
extends.")]
public byte Strength
{
get
{
return((_ViewState["GlowStrength"]
== null) ? (byte)6 :
(byte)_ViewState["GlowStrength"]);
}
set
{
if (value<1)
value=1;
_ViewState["GlowStrength"]
= value;
}
}
public override string ToString()
{
//For appearances
in the property window
if (this.Visible)
return "Visible" ;
else return
"Not Visible";
}
public String
GetStyle()
{
StringBuilder sb =
new StringBuilder();
if (this.Visible)
{
sb.Append("
progid:DXImageTransform.Microsoft.Glow(");
sb.Append("color="+
this.Color.ToKnownColor() + ",");
sb.Append("strength="+
this.Strength.ToString() + ") ");
sb.Append(System.Environment.NewLine);
return
sb.ToString();
}
else
{
return
string.Empty;
}
}
}
End Listing One