Introduction
The control explained in the article is a label with added shadow effect and animation for transparency, zoom, and rotation. This was done in C# and .NET 2.0 (tested in .NET 3.5 and works fine).
Background
I needed a text control with cool effects for a project, and I decided to make a standalone control.
Using the code
You can use the control in two standard ways:
- Include the project 'dzzControls' in your solution so it automatically appears in the Toolbox (in the dzzControls section), but it has a generic icon.
- In the Toolbox, right click on 'General' and click 'Choose items...'. Click 'Browse' and find 'dzzControls.dll'. Make sure the checkbox next to 'EffectsLabel' is ticked, and click OK.
Now, drag and drop it onto the form. In Properties, choose the 'Categorized' view and go to the 'Effects' section. Play with the properties (changes are immediately visible in the designer).
The control exposes a few properties:
ShadowColor
, combined with ForeColor
and BackColor
affect the colorization of the control.
ShadowColor
makes sense only if ShadowOffset
differs from the default 0; 0
. X and Y values can be negative!
- If
MinZoom
is below 100, the control will be animated with a gradual zoom between 100% and the set minimum percentage.
- If
MinAlpha
is less than MaxAlpha
, the control will be animated with a gradually changing transparency between the given values.
- If
MinRotate
is less than MaxRotate
, the control will be animated with a gradually rotating text between the specified angles (in degrees). Note: these values are limited to sbyte
(-128 to +127), so changing these to match your own needs is an easy exercise.
Letterwise
specifies whether effects are applied to individual letters or to the whole string (the Text
property inherited from the Control
).
A bit under the hood
The inherited properties Text
, Font
, ForeColor
, and Enabled
are overriden to accomodate control-specific logic.
The new properties (mentioned above) are all implemented with private variables, and setters usually contain some code (to refresh the control or similar). For example:
private byte _MinZoom;
[Category("Effects"), DefaultValue((byte)100),
Description("Animate text zoom, in range MinZoom%-100%")]
public byte MinZoom
{
get { return _MinZoom; }
set
{
_MinZoom = value <= 100 ? value : (byte)100;
if (_MinZoom == 0)
_MinZoom = 1;
if (_CurrentZoom < _MinZoom)
_CurrentZoom = _MinZoom;
if (_MinZoom < 100)
{
aTimer.Start();
}
else
RecalculateTimer();
Refresh();
}
}
There are also private variables which contain the current state of the animation, like:
private byte _CurrentZoom;
private sbyte _ZoomStep = 1;
Animation is achieved using an internal timer (called aTimer
, with a timeout of 20ms - equivalent of 50fps). When the timer ticks, one step of the animation is performed:
private void aTimer_Tick(object sender, EventArgs e)
{
...
if (_MinZoom < 100)
{
if (_CurrentZoom == 100)
_ZoomStep = (sbyte)-1;
if (_CurrentZoom == _MinZoom)
_ZoomStep = (sbyte)1;
_CurrentZoom = (byte)(_CurrentZoom + _ZoomStep);
}
Refresh();
}
The internal procedure RecalculateSize
re-adjusts the size of the control when the text, font, min or max rotation changes. It also calculates the size of individual characters, and those sizes are used in the paint event handler.
The brains of the this custom control is the OnPaint
event handler, and is way too large to be completely explained here, but the basic version is:
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.TranslateTransform(Size.Width / 2, Size.Height / 2);
if (_CurrentRotate != 0)
e.Graphics.RotateTransform(_CurrentRotate);
if (_CurrentZoom < 100)
e.Graphics.ScaleTransform(_CurrentZoom / 100.0f, _CurrentZoom / 100.0f);
e.Graphics.DrawString(Text, Font, new SolidBrush(base.ForeColor),
-tsize.Width / 2, -tsize.Height / 2);
Note: most of the properties are integral types (int
, byte
, sbyte
). This was sufficient for my current needs, changing these to float
s should be a nice exercise for the reader.
Some problems
When extra long strings are used (which I encountered during the use of this control), the control tends to put a high load on the processor if animation is enabled.
More than one of these controls on the form also tends to produce high processor usage if animation is enabled or controls are being moved over the form (and thus repeatedly redrawn).
Points of interest
The limitation of MeasureCharacterRanges
to only 32 measured ranges made me rewrite the OnPaint
for the letterwise case (after some time spent on debugging).
As this is my first control, I also spent quite some time on making the control 'Designer-friendly', e.g.:
[Category("Effects"), DefaultValue(typeof(Point), "0, 0"),
Description("If differs from (0,0), creates shadow" +
" with specified offset from Left and Top")]
public Point ShadowOffset
{
get { ...
... so when not set, this property is not bolded in the Visual Studio properties grid.
History
- The first version of the article was written on 11th March 2008.
- 12th March 2008: Minor modification to source code + some unneeded properties are hidden from the Visual Studio designer property grid (used code provided by Bob Powell).
- 24th March 2008: Replaced old demo application with new one (the old one required the DLL with the control).