Introduction
An animation consists of a series of frames. To perform the animation, these frames are shown one after the other at a regular time interval. Here I will show a couple of animations based on Clip
property of UIelement
in WPF. Every UIelement
type object has a clip property. Here all the animations have been done using clip
property of UIelement
. In this way, you can do lots of animation. I think it is a powerful way of doing animation. Possibilities are endless. You can do most geometrical type of animations in 2D using this technique. The following animations are shown in this article using this way:
- Circle Animation
- Radial Animation
- Interlaced Animation
- Block Animation
- Waterfall Animation
How Does Geometry Define the Visible Region of a UIelement like Image?
Every UIelement
has a Property
named clip
, which is Geometry
type. You can set any geometry used to define the outline of the contents of an element. Only the area that is within the region of the geometry will be visible. Portion of UIelement
outside the geometry will be visually clipped in the rendered layout. In the following, an Image
element is shown without a defined clip region and with a defined clip region.
Figure: Image with no Clip Region
Figure: Image with a ellipse Clip Region
Basic Idea of All Animations
The main theme of all animations is based on changing clip region of UIelement
at regular time interval. A single or composite geometry defines the clip region of UIelement
. So changing the geometry or combining a new geometry to the existing geometry at regular time interval will make an illusion of animation. Here for all animations, Geometry defines the visible region of a UIelement
like Image
. If we change the clip geometry at regular time interval, we will get different visible region of Image
. By changing the clip geometry at regular time interval, we will get a sequence of frames, which looks like an animation. So here, all the animations are based on a single or composite geometry object, which are changing at regular time interval in order to create an illusion of movement.
Why Property Based Animation of WPF Does Not Work Sometimes?
WPF has a new dependency property-based animation system. Every property based animation acts on a single dependency property and changes its value according to timeline. According to MSDN, for a property to have animation capabilities, it must meet the following three requirements:
- It must be a dependency property.
- It must belong to a class that inherits from
DependencyObject
and implements the IAnimatableinterface
.
- There must be a compatible animation type available.
However, there are some animations, whereby only changing the value of dependency property you cannot achieve the desired animation. For example, using property based animation you cannot add or remove any new geometry object/UI object. In those cases, you have to make animation using dispatcher timer, which runs in UI thread. In this article, only circle animation uses property based animation and for rest of the animations, Dispatcher timer has been used.
Circle Animation
This animation is like the circle animation of Microsoft PowerPoint. You can set any geometry like ellipse geometry to define the visible region of a WPF UIelement
type. To make it possible, every WPF UIelement
type has a clip property and you can set any geometry object to clip. However, for circle animation, we need circle geometry but WPF does not have built-in circle geometry.
How Can We Make Circle Geometry in WPF from Ellipse Geometry?
An Ellipse Geometry is defined by a center point, an x-radius and a y-radius. As we know, an ellipse geometry has two radius: radiousx
and radiousy
. If we keep the value of these two radiuses same, it will behave as circle. Just we will keep Radiousx = Radiousy
all the time.
How this Animation Effect is Achieved?
This trick is applicable to any UIelement
type. Here I have taken image as the Uielement
type to implement the circle animation. I have calculated the height and width of the image. Then set the centre of circle as (width/2, height/2) to start the animation from centre point. At first, I have kept the radius of the circle to 0. Then increment the radius proportion to time. For animation, I have used double animation. The code for this is in the following:
class CircleAnimation
{
public void MakeCircleAnimation(FrameworkElement animatedElement,
double width, double height, TimeSpan timeSpan)
{
EllipseGeometry ellipseGeometry = new EllipseGeometry();
ellipseGeometry.RadiusX = 0;
ellipseGeometry.RadiusY = 0;
double centrex = width / 2;
double centrey = height / 2;
ellipseGeometry.Center = new Point(centrex, centrey);
animatedElement.Clip = ellipseGeometry; double halfWidth = width / 2;
double halfheight = height / 2;
DoubleAnimation a = new DoubleAnimation();
a.From = 0;
a.To = Math.Sqrt(halfWidth * halfWidth + halfheight * halfheight);
a.Duration = new Duration(timeSpan);
ellipseGeometry.BeginAnimation(EllipseGeometry.RadiusXProperty, a);
ellipseGeometry.BeginAnimation(EllipseGeometry.RadiusYProperty, a);
}
}
For circle animation, I have used WPF property based animation, as I do not need to add or remove any object at runtime. Here DoubleAnimation
is applied both on EllipseGeometry.RadiusXProperty
, EllipseGeometry.RadiusYProperty
, and the value of x radius and y radius is incremented equally so that it behaves as a circle. Next thing is how much will I increase the radius.
How is the Radius of Clip Circle Calculated?
As I am applying animation on the image and it is rectangular shape, if a circle wants to fill the rectangle, its diameter have to be equal to rectangle’s diagonal line. The radius of the circle will be half of the diagonal line of rectangle. So I have applied Pythagorean theorem to calculate the radius of clip circle as shown in the below figure.
Figure: Pythagorean Theorem to calculate the radius of clip circle
Radial Animation (Wheel Clockwise, 1 Spoke, Animation of Microsoft PowerPoint)
This animation is like the Wheel Clockwise, 1 spoke, animation of Microsoft PowerPoint. Some people also call this animation as Radial animation. This animation starts from a line, which points from the centre to top middle, and then it rotates 360 degrees in clockwise direction and makes the underlying object visible. I know one picture can tell what the animation looks like instead of writing many sentences. The animation looks like the following:
How this Animation Effect is Achieved?
As we know, every WPF UIelement
type has a clip property and you can set any geometry object to clip. I have used this clip property to make this animation. I have changed the geometry object with time so that it looks like a wheel animation. All the challenges to make this animation in this way is making the exact geometry object with time.
I am showing this animation for Image
type, which is a Uielement
type. However, this trick is applicable to any UIelement
type. PathGeometry
is used here as the geometry to clip the image. Line segments are added to the path geometry dynamically to make the visible region bigger with time. I have calculated the height and width of the image. Then set the first line segment from (width/2, height/2) to (Width/2, 0). Then added the other line segments when they are needed and also change their points. For this animation, I have used DispatcherTimer
, which runs in UI thread instead of worker thread. The code for this is in the following:
class RadialAnimation
{
DispatcherTimer _timer = null;
Ellipse _grdMain = null;
ContentPresenter _circleContentPresenter = null;
LineSegment LineSegment2 = null;
PathFigure PathFigure1 = null;
bool ISIncrementdirectionX = true;
bool IsIncrementX = true;
bool IsIncrementY = true;
LineSegment LineSegmentFirstcorner = null;
LineSegment LineSegmentseconcorner = null;
LineSegment LineSegmentThirdcorner = null;
LineSegment LineSegmentFourthcorner = null;
double _ofsetOfAnimation = 1;
double _height = 0;
double _width = 0;
public void MakeRadiaAnimation(FrameworkElement animatedElement,
double width, double height, TimeSpan timeSpan)
{
_height = height;
_width = width;
double steps = 2 * (_height + _width);
double tickTime = timeSpan.TotalSeconds / steps;
string topCentre = Convert.ToString(_width / 2) + ",0";
string centre = Convert.ToString(_width / 2) +
"," + Convert.ToString(_height / 2);
PathGeometry PathGeometry1 = new PathGeometry();
PathFigure1 = new PathFigure();
PathFigure1.StartPoint =
((Point)new PointConverter().ConvertFromString(centre));
PathGeometry1.Figures.Add(PathFigure1);
LineSegment LineSegmentdummy = new LineSegment();
LineSegmentdummy.Point =
((Point)new PointConverter().ConvertFromString(topCentre));
PathFigure1.Segments.Add(LineSegmentdummy);
LineSegmentseconcorner = new LineSegment();
LineSegmentseconcorner.Point =
((Point)new PointConverter().ConvertFromString(topCentre));
PathFigure1.Segments.Add(LineSegmentseconcorner);
LineSegmentThirdcorner = new LineSegment();
LineSegmentThirdcorner.Point =
((Point)new PointConverter().ConvertFromString(topCentre));
PathFigure1.Segments.Add(LineSegmentThirdcorner);
LineSegmentFourthcorner = new LineSegment();
LineSegmentFourthcorner.Point =
((Point)new PointConverter().ConvertFromString(topCentre));
PathFigure1.Segments.Add(LineSegmentFourthcorner);
LineSegmentFirstcorner = new LineSegment();
LineSegmentFirstcorner.Point =
((Point)new PointConverter().ConvertFromString(topCentre));
PathFigure1.Segments.Add(LineSegmentFirstcorner);
LineSegment2 = new LineSegment();
LineSegment2.Point =
((Point)new PointConverter().ConvertFromString(topCentre));
PathFigure1.Segments.Add(LineSegment2);
animatedElement.Clip = PathGeometry1;
PointAnimationUsingKeyFrames pointAnimationUsingKeyFrames =
new PointAnimationUsingKeyFrames();
DiscretePointKeyFrame discretePointKeyFrame = new DiscretePointKeyFrame();
discretePointKeyFrame.Value = new Point(0, 0);
TimeSpan keyTime = new TimeSpan(0, 0, 1);
discretePointKeyFrame.KeyTime = keyTime;
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(tickTime);
_timer.Tick += new EventHandler(_timer_Tick);
_timer.IsEnabled = true;
}
void _timer_Tick(object sender, EventArgs e)
{
if ((LineSegment2.Point.X <= 0) && (LineSegment2.Point.Y <= 0))
{
LineSegmentFirstcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
ISIncrementdirectionX = true;
IsIncrementX = true;
}
else if (((LineSegment2.Point.X >= _width)
&& (LineSegment2.Point.Y <= 0)))
{
LineSegmentseconcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
LineSegmentThirdcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
LineSegmentFourthcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
LineSegmentFirstcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
ISIncrementdirectionX = false;
IsIncrementY = true;
}
else if ((LineSegment2.Point.X >= _width)
&& (LineSegment2.Point.Y >= _height))
{
LineSegmentThirdcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
LineSegmentFourthcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
LineSegmentFirstcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
ISIncrementdirectionX = true;
IsIncrementX = false;
}
else if ((LineSegment2.Point.X <= 0)
&& (LineSegment2.Point.Y >= _height))
{
LineSegmentFourthcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
LineSegmentFirstcorner.Point =
new Point(LineSegment2.Point.X, LineSegment2.Point.Y);
ISIncrementdirectionX = false;
IsIncrementY = false;
}
double x = 0, y = 0;
if (ISIncrementdirectionX == true)
{
if (IsIncrementX)
{
x = LineSegment2.Point.X + _ofsetOfAnimation;
y = LineSegment2.Point.Y;
}
else
{
x = LineSegment2.Point.X - _ofsetOfAnimation;
y = LineSegment2.Point.Y;
}
}
else
{
if (IsIncrementY)
{
x = LineSegment2.Point.X;
y = LineSegment2.Point.Y + _ofsetOfAnimation;
}
else
{
x = LineSegment2.Point.X;
y = LineSegment2.Point.Y - _ofsetOfAnimation;
}
}
LineSegment2.Point = new Point(x, y);
if ((LineSegment2.Point.X == _width / 2)
&& (LineSegment2.Point.Y == 0))
{
_timer.IsEnabled = false;
}
}
}
Block Animation
Here Path geometry is used as clip geometry of the UIelement
(Image
). At first, the path geometry is blank, so nothing is shown in the display. If you look at the Block animation, it looks like a set of rectangles is used to create the animation. These rectangles are added dynamically to the path geometry at regular time interval. Here one rectangle is added at a time and the position of rectangle is chosen at random. For that reason, I have segmented the UIelement
into a certain number of cells.
I have selected the cell going to be visible next at random. Then convert this cell number to row and column number using the following formula:
Row number = (cell Number-1) / number of cells in a row +1 Column Number =
(cell Number-1) % number of cells in a row +1
Here each square (rectangle) has equal explicit height and width. Then I have made the rectangle for this using the following formula:
new Rect( row number * Size, column number * Size, Size, Size);
Then I have performed the union combine operation between the newly created rectangle and path geometry using the following line:
pathGeometry = Geometry.Combine(pathGeometry, myRectGeometry2,
GeometryCombineMode.Union, null);
At last, I have reset the clip geometry of image to the modified path geometry. I have done the above thing in each tick handler of dispatcher timer. Therefore, I have gotten different path geometry in each tick and get the animation. The used code for this is in the following:
class BlockAnimation
{
DispatcherTimer _timer = null;
PathGeometry pathGeometry = null;
double _rectangleSize = 80;
double _numberofrectangles = 0;
RandomNumberFromAGivenSetOfNumbers rdm = null;
int _numberOfalreadyDrawnRectangles = 0;
double _height = 0;
double _width = 0;
FrameworkElement _animatedElement = null;
public void MakeBlockAnimation(FrameworkElement animatedElement,
double width, double height, TimeSpan timeSpan)
{
_animatedElement = animatedElement;
_height = width;
_width = height;
if (_height > _width)
{
_numberofrectangles = Math.Ceiling(_height / _rectangleSize);
}
else
{
_numberofrectangles = Math.Ceiling(_width / _rectangleSize);
}
rdm = new RandomNumberFromAGivenSetOfNumbers(0,
Convert.ToInt32(_numberofrectangles * _numberofrectangles - 1));
double steps = _numberofrectangles * _numberofrectangles;
double tickTime = timeSpan.TotalSeconds / steps;
pathGeometry = new PathGeometry();
_animatedElement.Clip = pathGeometry;
_timer = new DispatcherTimer(DispatcherPriority.Input);
_timer.Interval = TimeSpan.FromSeconds(tickTime);
_timer.Tick += new EventHandler(_timer_Tick);
_timer.IsEnabled = true;
}
void _timer_Tick(object sender, EventArgs e)
{
int random = rdm.Next();
int i = random / (int)_numberofrectangles;
int j = random % (int)_numberofrectangles;
RectangleGeometry myRectGeometry2 = new RectangleGeometry();
myRectGeometry2.Rect = new Rect(j * _rectangleSize,
i * _rectangleSize, _rectangleSize, _rectangleSize);
pathGeometry = Geometry.Combine(pathGeometry,
myRectGeometry2, GeometryCombineMode.Union, null);
_animatedElement.Clip = pathGeometry;
if (_numberOfalreadyDrawnRectangles ==
_numberofrectangles * _numberofrectangles)
{
_timer.IsEnabled = false;
}
_numberOfalreadyDrawnRectangles++;
}
}
To get random number from a specific range, I have made a class and used it here.The Random
class defined in the .NET Framework provides functionality to generate pseudo-random number. According to MSDN, the current implementation of the Random
class is based on Donald E. Knuth's subtractive random number generator algorithm. The random number generation starts from a seed value. If the same seed is used repeatedly, the same series of numbers is generated. One way to produce different sequences is to make the seed value time-dependent, thereby producing a different series with each new instance of Random
. However, current Random
class implementation of .NET Framework does not provide the following functionality: Picking random numbers from a range of numbers with no repetition: Current implementation returns random numbers within a range with some duplicate number. For example, you want random number from a range of numbers like (4 to 21). You would like to pick one number at a time at random from that range without any repetition or duplicate However, this feature is easy to implement by using the Random
class of the .NET Framework. I have needed this functionality for this animation. The used code for this is in the following:
class RandomNumberFromAGivenSetOfNumbers
{
List<int> _setOfNumbers = new List<int>();
Random _random = new Random();
public RandomNumberFromAGivenSetOfNumbers(int min, int max)
{
for (int i = min; i <= max; i++)
{
_setOfNumbers.Add(i);
}
}
public int Next()
{
if (_setOfNumbers.Count > 0)
{
int nextNumberIndex = _random.Next(_setOfNumbers.Count);
int val = _setOfNumbers[nextNumberIndex];
_setOfNumbers.RemoveAt(nextNumberIndex);
return val;
}
return -1;
}
}
Interlaced Animation
When Interlacing animation is applied on image or any UIelement
then the image/UIelement
is drawn in a series of passes rather than all at the same time. The first pass skips a certain number of lines, then on consecutive passes the lines, which were skipped, are filled in. I know one picture can tell what is the animation looks like instead of writing many sentences. The animation looks like the following:
Now I will discuss how I came up to implement interlace animation with WPF. This animation has nothing to do with monitor interlacing. Here I am simulating monitor interlacing process in WPF. To implement interlace animation; I have used a set of rectangles geometry of WPF. As we know, in the first pass of interlace animation, I have drawn a number of small height rectangle from top and bottom to fill the image skipping a single line height after each rectangle. Then in the second pass of interlace animation, I have drawn again a number of small rectangles in the direction to top and bottom starting from centre. This time, the code does not keep any blank line between two consecutive small rectangles. Here path geometry is used as the clip geometry. Initially this path geometry is blank. At the first pass, I have added a number of small rectangles with path geometry at regular time interval using combine union operation from top to bottom to fill the image skipping a single line height after each rectangle. At the second pass, I have added another number of small rectangles with path geometry at regular time interval using combine union operation in the direction of top and bottom starting from centre. The code for this is in the following:
class InterlacedAnimation
{
DispatcherTimer _timer = null;
PathGeometry pathGeometry = null;
double _rectangleSize = 3;
double _startingCoordinate = 0;
double _endcoordinate = 0;
FrameworkElement _animatedElement = null;
double _width = 0;
double _height = 0;
double _offset = 0.5;
bool IsTimerShouldBeStoppedForUp = false;
bool IsTimerShouldBeStoppedForDown = false;
public void MakeInterlacedAnimation(FrameworkElement animatedElement,
double width, double height, TimeSpan timeSpan)
{
double steps = (height / (_rectangleSize + _offset));
double tickTime = timeSpan.TotalSeconds / steps;
_animatedElement = animatedElement;
_width = width;
_height = height;
_endcoordinate = height;
pathGeometry = new PathGeometry();
animatedElement.Clip = pathGeometry;
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(tickTime);
_timer.Tick += new EventHandler(_timer_Tick);
_timer.IsEnabled = true;
}
void _timer_Tick(object sender, EventArgs e)
{
if (IsTimerShouldBeStoppedForUp == true &&
IsTimerShouldBeStoppedForDown == true)
{
_timer.IsEnabled = false;
}
if (_startingCoordinate == _endcoordinate)
{
_offset = 0;
}
if (_startingCoordinate >= (_height / 2 - 5))
_offset = 0;
if (_endcoordinate <= (_height / 2 - 5))
_offset = 0;
if (_startingCoordinate < _height)
{
RectangleGeometry myRectGeometry2 = new RectangleGeometry();
myRectGeometry2.Rect = new Rect(0, _startingCoordinate,
_width, _rectangleSize);
pathGeometry = Geometry.Combine(pathGeometry,
myRectGeometry2, GeometryCombineMode.Union, null);
}
else
{
IsTimerShouldBeStoppedForUp = true;
}
if (_endcoordinate > 0)
{
RectangleGeometry myRectGeometry3 = new RectangleGeometry();
myRectGeometry3.Rect =
new Rect(0, _endcoordinate, _width, _rectangleSize);
pathGeometry = Geometry.Combine(pathGeometry,
myRectGeometry3, GeometryCombineMode.Union, null);
}
else
{
IsTimerShouldBeStoppedForDown = true;
}
_startingCoordinate = _startingCoordinate + _rectangleSize + _offset;
_endcoordinate = _endcoordinate - _rectangleSize - _offset;
_animatedElement.Clip = pathGeometry;
}
}
As I am adding a number of small rectangles dynamically at runtime, property based animation does not work here. For that reason, I have used dispatcher timer based animation. I am using here dispatcher timer instead of classic Timer as dispatcher timer runs in UI thread and I do not need to write thread switching code. This trick is applicable to any UIelement
type. Here I have taken image as the UIelement
type to implement the interlace animation. I have calculated the height and width of the image. Using Combine
method of Geometry
class, we can combine two geometries using the specified geometryCombineMode
. GeometryCombineMode
can be Union
, Intersect
, Exclude
, or X
or
. I have added all the small rectangles to the path geometry by doing union operation.
Waterfall Animation
I know one picture can tell what the waterfall animation looks like instead of writing many sentences. The animation looks like the following:
As we know, every WPF UIelement
type has a clip property and you can set any geometry object to clip. I have used this clip property to make this animation. I have changed the geometry object with time so that it looks like a Waterfall animation. All the challenges to make this animation in this way is making the exact geometry object with time. In waterfall animation, a number of small rectangles are drawn to fill the width with some gape and these rectangle moves vertically to the bottom. The height of rectangles is randomly taken between 0 and 25 and the random value for a particular rectangle height is changed with time. The main theme to achieve this animation is clip geometry. As I am changing the clip geometry at runtime, property based animation does not work here. For that reason, I have used dispatcher timer based animation. I am using here dispatcher timer instead of classic Timer as dispatcher timer runs in UI thread and I do not need to write thread switching code. The used C# code for Roll animation is in the following:
class WaterFallAnimation
{
DispatcherTimer _timer = null;
PathGeometry pathGeometry = null;
double _rectangleSize = 10 + 50;
double _offset = 5;
int _waterFallHeight = 50;
double _width = 0;
double _height = 0;
Random random = new Random();
FrameworkElement _animatedElement = null;
RectangleGeometry myRectGeometry2 = null;
PathGeometry pathGeometry2 = null;
RectangleGeometry myRectGeometry5 = null;
public void MakeWaterFallAnimation(FrameworkElement animatedElement,
double width, double height, TimeSpan timeSpan)
{
_animatedElement = animatedElement;
_height = height;
_width = width;
myRectGeometry2 = new RectangleGeometry();
pathGeometry2 = new PathGeometry();
myRectGeometry5 = new RectangleGeometry();
TranslateTransform translateTransform = new TranslateTransform();
translateTransform.X = 0;
translateTransform.Y = 10;
double steps = (_height / _offset);
double tickTime = timeSpan.TotalSeconds / steps;
pathGeometry = new PathGeometry();
animatedElement.Clip = pathGeometry;
_timer = new DispatcherTimer(DispatcherPriority.Input);
_timer.Interval = TimeSpan.FromSeconds(tickTime);
_timer.Tick += new EventHandler(_timer_Tick);
_timer.IsEnabled = true;
}
void _timer_Tick(object sender, EventArgs e)
{
myRectGeometry2.Rect = new Rect(0, 0, _width, _rectangleSize);
pathGeometry2 = Geometry.Combine
(pathGeometry2, myRectGeometry2, GeometryCombineMode.Union, null);
for (int i = 1; i <= _width; i = i + 2)
{
myRectGeometry5.Rect = new Rect(new Point(i, _rectangleSize),
new Point(i + 2, _rectangleSize - random.Next(0, _waterFallHeight)));
pathGeometry2 = Geometry.Combine(pathGeometry2,
myRectGeometry5, GeometryCombineMode.Exclude, null);
}
_animatedElement.Clip = pathGeometry2;
if (_rectangleSize == _height + _waterFallHeight)
{
_timer.IsEnabled = false;
}
_rectangleSize += _offset;
}
}
Here I have used path geometry as clip geometry of Image
. This path geometry is the basis of this animation. The myRectGeometry2
is used to make visible certain portion of image without rainfall. Then a union operation is performed between myRectGeometry2
and path geometry. At each timer tick, I have increased the height of myRectGeometry2
. Then I have drawn a number of small rectangles with width 2 and height random using a for
-loop. These rectangles are drawn using two points. In the for
-loop, I have created Rectangle Geometry myRectGeometry5
and perform the union operation with path geometry. Then the clip property of the image is reset to the modified path geometry. By this way, I have gotten modified path geometry at each tick of dispatcher timer and used that path geometry to get this animation.
Known Issue
Here most of the animations have been done using dispatcher timer, which is not guaranteed to fire in exact time and runs in UI thread. To make the animation time controlled using dispatcher timer, you can adjust the change (offset) of animation in each step. On the other hand, to make the animation time controlled, you can use System.timer
instead of dispatcher timer, which is guaranteed to execute in exact time and run in different thread instead of UI thread. If you use System.timer
, you have to perform cross thread operation for UI update. To keep code simple, I have not implemented System.timer
here.
Conclusion
Thanks for reading this write up. Hope this will save some of your time. If you guys have any questions, I will love to answer. I always appreciate comments. If you find any issues, please let me know. I will try my level best to solve the issues.
History
- Initial release – 30/12/09