|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionNowadays, RSS feeds are quite common. I was looking for a Windows control, a rotator control to be used for displaying data from an RSS feed, but couldn't find anything. So my next step was to write something to do the job for me. The idea is to have a control to rotate some frames displaying my data. Using the codeIn order to be able to use the control, you should add a reference to the Rotator.dll file in your project. Once that is done, you will be able to see the control available in the toolbox window, and you can drag and drop it onto your form. The frames are moved on the X or Y axis, down-to-up/right-to-left. Having the animation the other way around up-to-down/left-to-right, you will have to change the sign of the value telling how much to move the frames during animation. In order to see some movement there, you need to add/insert some data into the
Control propertiesThe control provides the following properties:
ArchitectureThe main classes defined in this assembly are presented in the following list:
I will explain now a little bit of how this control works, leaving the code to do the rest. I will start with the animation, and explain how it is achieved. The idea behind it is to force repainting. The //create the notifier
this.repaintNotifier =
new EventNotifier(template.TextAnimationDelay,
new NotifierEvent(OnNotifierEvent));
Whenever the animation flag is enabled for the frame, the notifier is set to raise events. With every notification made, the main text area of the frame is invalidated, and the control is told to redraw its invalidated part of the client area. private void OnNotifierEvent(object sender, EventArgs args)
{
this.repaintNotifier.Pause = true;
if (this.Handle.ToInt32() > 0)
{
//invoke the delegate; thread safe access
this.Invoke(RepaintNotifyHandler);
}
}
private void Repaint()
{
this.Invalidate(new Region(infoPath));
this.Update();
}
The protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
//set up some flags
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
e.Graphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
//check to see if the path has been initialized
if (graphicPath == null)
{
InitializeGraphicPath();
}
//draw header
e.Graphics.FillPath(frameTemplate.HeaderBrush, headerPath);
//draw header
e.Graphics.FillPath(frameTemplate.InformationBrush, infoPath);
//is there data linked to this object
if (null != data)
{
RectangleF outputHeaderText = headerPath.GetBounds();
float square = Math.Min(outputHeaderText.Width / 2,
outputHeaderText.Height);
//do we need to resize the image
if (bufferAdjusment)
{
if (null != localImage)
{
localImage.Dispose();
}
if (data.Image != null)
{
localImage = (Image)data.Image.Clone();
//resize the image displayed in the header
if (localImage.Width > square || localImage.Height >
outputHeaderText.Height)
{
int maxOne = (int)(Math.Max(0, square * 100 /
localImage.Width));
int maxTwo = (int)(Math.Max(0,
outputHeaderText.Height * 100 /
localImage.Height));
maxOne = Math.Min(maxOne, maxTwo);
localImage = ImageResize.Resize(localImage, maxOne);
}
}
}
if (null != localImage)
{
outputHeaderText =
new RectangleF(outputHeaderText.Left +
localImage.Width + (square - localImage.Width) / 2,
outputHeaderText.Top, outputHeaderText.Width -
localImage.Width, outputHeaderText.Height);
e.Graphics.DrawImage(localImage,
new PointF((Math.Max( square, outputHeaderText.Width / 2) -
localImage.Width) / 2,
(outputHeaderText.Height - localImage.Height) / 2));
}
//recalculate the text size if necessary
if (bufferAdjusment)
{
bufferedTextSize = e.Graphics.MeasureString(data.HeaderText,
frameTemplate.HeaderFont,
outputHeaderText.Size, stringFormat);
bufferAdjusment = false;
}
//set the header area
outputHeaderText =
new RectangleF(new PointF(outputHeaderText.X +
(outputHeaderText.Width - bufferedTextSize.Width) * 0.5f,
outputHeaderText.Y + (outputHeaderText.Height -
bufferedTextSize.Height) * 0.5f), bufferedTextSize);
//render the header text
e.Graphics.DrawString(data.HeaderText, frameTemplate.HeaderFont,
new SolidBrush(frameTemplate.HeaderTextColor),
outputHeaderText, stringFormat);
}
//is animation enabled
if (enableTextAnimation)
{
//initialize animation if necessary
if (null == textAnimation)
{
InitializeTextAnimation();
}
//set the graphics object
textAnimation.Graphics = e.Graphics;
//render the main text
textAnimation.DrawText();
textAnimation.Graphics = null;
this.repaintNotifier.Pause = false;
}
else
{
//pause the repaint event notifier
this.repaintNotifier.Pause = true;
}
}
As I have said, the frame calls the animation in its paint handler to draw the text. But how is the typing text animation accomplished? The animation object is passed the text to be animated whenever new data is available for the frame, as well as the available rectangle to draw the text. An index is stored pointing to the current position within the given text; a call to the public override void DrawText()
{
if (text != null)
{
if (null == brush)
{
brush = new SolidBrush(textColor);
}
//calculate the text size if needed
if (measureText)
{
measureText = false;
//stringFormat.LineAlignment = StringAlignment.Center;
SizeF size = graphics.MeasureString(text, font,
area.Size, stringFormat);
//center the text within the given rectangle
float widthAdjustment = (area.Width - size.Width) / 2;
float heightAdjustment = (area.Height - size.Height) / 2;
//set the new area
area = new RectangleF(new PointF(area.X + widthAdjustment,
area.Y + heightAdjustment), size);
}
if (index >= text.Length)
{
graphics.DrawString(text, font, brush, area);
//raise the event if necessary
if (null != AnimationFinished && !eventSignaled)
{
AnimationFinished(this, new EventArgs());
eventSignaled = true;
}
}
else
{
//draw part of the text
graphics.DrawString(text.Substring(0, index),
font, brush, area);
//set the new step of the animation
index++ ;
if (index > text.Length)
{
index = text.Length;
}
SizeF sizeExceed =
graphics.MeasureString(text.Substring(0, index),
font, (int)area.Width, stringFormat);
//if text exceeds the available area don't draw it
if (sizeExceed.Height > area.Height)
{
//get last empty character
//of the text being rendered and add the "..."
int lastSpace = text.LastIndexOf(' ');
if (lastSpace == text.Length - 1)
{
lastSpace = text.LastIndexOf(' ', lastSpace);
}
if (lastSpace < 0)
{
lastSpace = 0;
}
text = text.Substring(0, lastSpace) + "...";
index = text.Length;
}
}
}
}
As mentioned earlier, the private void HandleNotification()
{
//move frames
this.SuspendLayout();
if (animationMode == RotatorControlAnimationMode.YAxis)
{
int landMark = Height / 8;
landMark = landMark - (Height - 8 * landMark);
if ((animationStep >= 0 && (secondFrame.Top <= landMark))
|| (animationStep < 0 && (secondFrame.Top >= landMark)))
{
StopFrameAnimation();
}
else
{
if (animationStep >= 0)
{
if (secondFrame.Top - animationStep <= landMark)
{
firstFrame.Top = landMark - Height;
secondFrame.Top = landMark;
}
else
{
firstFrame.Top -= (int)animationStep;
secondFrame.Top -= (int)animationStep;
}
}
else
{
if (secondFrame.Top - animationStep >= landMark)
{
firstFrame.Top = landMark - Height;
secondFrame.Top = landMark;
}
else
{
firstFrame.Top -= (int)animationStep;
secondFrame.Top -= (int)animationStep;
}
}
}
}
else
{
int landMark = Width / 4;
landMark = landMark - (Width - 4 * landMark);
if ((animationStep >= 0 &&
(secondFrame.Left - animationStep < 0)) ||
(animationStep < 0 && (secondFrame.Left -
animationStep > 0)))
{
StopFrameAnimation();
}
else
{
firstFrame.Left -= (int)animationStep;
secondFrame.Left -= (int)animationStep;
}
}
this.ResumeLayout(false);
}
private void StopFrameAnimation()
{
//animation is stopped
animating = false;
//stop the background thread raising the notification events
frameAnimationNotifier.Stop(true);
//is there a change to the collection!?
if (null == queuedChange)
{
//swap frames
SwapRotatorFrames();
//set the frames location
SetFramesPosition();
//repaint first frame
firstFrame.Refresh();
}
else
{
//there is a change to the collection
HandleItemsCollectionChanged();
}
}
Because the data collection can be changed while the frames are being animated (and not only then), the private void OnItemsCollectionChanged(object sender,
CollectionChangeArgs args)
{
//if animating store the change
if (animating)
{
if (null == queuedChange)
{
queuedChange = args;
}
}
else
{
//if not animationg do the updates
queuedChange = args;
HandleItemsCollectionChanged();
}
}
private void HandleItemsCollectionChanged()
{
//is there a change to the collection
if (queuedChange != null)
{
switch (queuedChange.ChangeType)
{
case ChangeType.ItemAdded:
if (queuedChange.Count == 1)
{
InitializeData();
firstFrame.EnableTextAnimation = true;
}
break;
case ChangeType.ItemsRemoved:
firstFrame.Data = null;
firstFrame.ResetText();
firstFrame.EnableTextAnimation = false;
firstFrame.Visible = false;
secondFrame.Data = null;
secondFrame.ResetText();
secondFrame.EnableTextAnimation = false;
secondFrame.Visible = false;
SetFramesPosition();
break;
case ChangeType.ItemUpdate:
if (currentIndex == queuedChange.Index)
{
firstFrame.Data = items[currentIndex];
}
break;
case ChangeType.ItemRemoved:
if (queuedChange.Count == 0)
{
firstFrame.Data = null;
firstFrame.ResetText();
firstFrame.EnableTextAnimation = false;
firstFrame.Visible = false;
secondFrame.Data = null;
secondFrame.ResetText();
secondFrame.EnableTextAnimation = false;
secondFrame.Visible = false;
SetFramesPosition();
}
else
{
if (currentIndex == queuedChange.Index)
{
BufferNextData(currentIndex);
SwapRotatorFrames();
BufferNextData(currentIndex + 1);
}
}
break;
}
queuedChange = null;
}
}
ConclusionMaybe this control will be handy for some developers out there. If you encounter any problems or require any enhancements, I will be happy to assist you, so any feedback would be appreciated. History
| ||||||||||||||||||||