Introduction
The control explained in the article is label with added shadow effect and animation for transparency, zoom and rotation. Done in C# and .NET 2.0 (tested in .NET 3.5 and works fine).
Background
I needed text control with cool effects for some other project, and I decided to make a standalone control.
Using the Code
You can use control in 2 standard ways:
- Include project 'dzzControls' in your solution, so it automatically appears in toolbox (in dzzControls section), but it has generic icon
- In toolbox, right click on 'General' and click 'Choose items...'. Click 'Browse' and find 'dzzControls.dll'. Make sure checkbox next to 'EffectsLabel' is ticked and click OK
Now drag&drop it onto the form. In properties, choose 'Categorized' view and go to 'Effects' section. Play with properties (changes are immediately visible in designer).
Control exposes few properties:
ShadowColor, combined with ForeColor and BackColor affect colorization of control.
ShadowColor makes sense only if ShadowOffset differs from default 0; 0. X and Y values can be negative!
- If
MinZoom is below 100, control will be animated with gradual zoom between 100% and set minimum percentage.
- If
MinAlpha is less than MaxAlpha, control will be animated with gradually changing transparency between given values.
- If
MinRotate is less than MaxRotate, control will be animated with gradually rotating text between specified angles (in degrees). Note: these values are limited to sbyte (-128 to +127), so changing these to match your own need is an easy excercise.
Letterwise - whether effects are applied to idividual letters, or the whole string (the Text property inherited from Control).
A bit under the hood
Inherited properties Text, Font, ForeColor and Enabled are overriden to accomodate control-specific logic.
New properties (mentioned above) are all implemented with private variables, and setters usually contain some code (to refresh 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 current state of animation, like:
private byte _CurrentZoom;
private sbyte _ZoomStep = 1;
Animation is achieved using internal timer (called aTimer with timeout 20ms - equvalent of 50fps). When timer ticks, one step of animation is perfomed:
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();
}
Internal procedure RecalculateSize re-adjusts size of the control when text, font, min or max rotation changes. Also calculates size of individual characters, and those sizes are used in paint event handler.
Brains of the this custom control is OnPaint event hander, and is way too large to be completely explained here, but 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 floats should be a nice excercise for the reader.
Some problems
When extra long strings are used (which I encountered during use of this control), control tend to put high load on the processor if animation is enabled.
More than one of these controls on the form also tend to produce high processor usage if animation is enabled and controls are being moved over the form (and thus repeatedly redrawn).
Points of Interest
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', eg:
[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 visual studio properties grid.