Winform 下无闪烁走马灯效果实现
Winform 下无闪烁走马灯效果实现
新一篇: 一个用C#编写的自动读写配置文件的开源组件
Winform 下无闪烁走马灯效果实现
作者:肖波
最近需要在Winform项目中实现一个走马灯的效果,一开始用了一些办法比如移动Label控件,效果总是不太好,移动文本时总有闪烁的现象。后来找了一个国外的开源控件,应用了一下,效果还不错。仔细阅读了一下代码,发现还有一些地方值得改进,现把代码以及改动说明贴出来,和大家分享。
控件出处:http://www.codeproject.com/KB/miscctrl/ScrollingTextControlArtic.aspx
我的改动:
1、DoubleBuffer 的设置
原代码中用的是 this.SetStyle(ControlStyles.DoubleBuffer, true); 但一些网友反映这个标志在.net 2.0 以上版本无效。说句老实话,我也不是特别确信,MSDN上也没有说明这一点。在我的.net 2.0 系统中,两种设置的效果似乎没有太多区别。在一个国外网站上找到他们的区别,下面是原文:
ControlStyles == CS
AllPaintingInWMPaint == APWMP
OptimizedDoubleBuffer = ODB
DoubleBuffer = DB
An earlier permutation of the design called for ODB to simply be a combination of DB, APWMP and UserPaint. Through several design changes, the two control styles are nearly synonymous, but they still have differences. Now that we've broken that, we may consider un-deprecating CS.DB to retain . Here is a more complete summary of the current design:
Mechanism Side effects Other flags required to work Require APWMP? Notes
ControlStyle
.DB none
APWMP, UserPaint
Yes
We are considering NOT deprecating this flag because ODB isn't an exact replacement for DB.
ControlStyle
.ODB none
none
No
Works, but without APWMP set you'll buffer foreground and background separately and will still see flicker.
Control
.DoubleBuffered sets CS.ODB, CS.APWMP
none
No
Works for most mainstream buffering needs. Getter is peculiar in that it only checks CS.ODB.
I'm following up on the need for deprecating CS.DB and Control.DoubleBuffered's getter and will post here.
总之保险起见,还是判一下版本号,下面是判断代码
Version v = System.Environment.Version;
if (v.Major < 2)
{
this.SetStyle(ControlStyles.DoubleBuffer, true);
}
else
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
2、刷新区域
原代码中刷新区域是这样设置的
private void Tick(object sender, EventArgs e)
{
//update rectangle to include where to paint for new position
//lastKnownRect.X -= 10;
//lastKnownRect.Width += 20;
lastKnownRect.Inflate(10, 5);
//create region based on updated rectangle
Region updateRegion = new Region(lastKnownRect);
//repaint the control
Invalidate(updateRegion);
Update();
}
lastKnownRect是文字的整个区域,如果文字较长,这个刷新区域就会比较大,但实际上我们只需要刷新控件显示范围内的区域就可以了。
所以这里改动如下:
//Controls the animation of the text.
private void Tick(object sender, EventArgs e)
{
//update rectangle to include where to paint for new position
//lastKnownRect.X -= 10;
//lastKnownRect.Width += 20;
lastKnownRect.Inflate(10, 5);
//get the display rectangle
RectangleF refreshRect = lastKnownRect;
refreshRect.X = Math.Max(0, lastKnownRect.X);
refreshRect.Width = Math.Min(lastKnownRect.Width + lastKnownRect.X, this.Width);
refreshRect.Width = Math.Min(this.Width - lastKnownRect.X, refreshRect.Width);
//create region based on updated rectangle
//Region updateRegion = new Region(lastKnownRect);
Region updateRegion = new Region(refreshRect);
//repaint the control
Invalidate(updateRegion);
Update();
}
2、修改Enabled属性
当Enabled设置为false时,原控件依然会滚动,觉得还是不让它滚动更好一些。
修改代码如下:
[
Browsable(true),
CategoryAttribute("Behavior"),
Description("Indicates whether the control is enabled")
]
new public bool Enabled
{
set
{
timer.Enabled = value;
base.Enabled = value;
}
get
{
return base.Enabled;
}
}
下面给出修改后完整的控件代码,代码原作者为jconwell,原始代码见前面提到的控件出处
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;
namespace ScrollingTextControl
{
/**////
/// Summary description for ScrollingTextControl.
///
[
ToolboxBitmapAttribute(typeof(ScrollingTextControl.ScrollingText), "ScrollingText.bmp"),
DefaultEvent("TextClicked")
]
public class ScrollingText : System.Windows.Forms.Control
{
private Timer timer; // Timer for text animation.
private string text = "Text"; // Scrolling text
private float staticTextPos = 0; // The running x pos of the text
private float yPos = 0; // The running y pos of the text
private ScrollDirection scrollDirection = ScrollDirection.RightToLeft; // The direction the text will scroll
private ScrollDirection currentDirection = ScrollDirection.LeftToRight; // Used for text bouncing
private VerticleTextPosition verticleTextPosition = VerticleTextPosition.Center; // Where will the text be vertically placed
private int scrollPixelDistance = 2; // How far the text scrolls per timer event
private bool showBorder = true; // Show a border or not
private bool stopScrollOnMouseOver = false; // Flag to stop the scroll if the user mouses over the text
private bool scrollOn = true; // Internal flag to stop / start the scrolling of the text
private Brush foregroundBrush = null; // Allow the user to set a custom Brush to the text Font
private Brush backgroundBrush = null; // Allow the user to set a custom Brush to the background
private Color borderColor = Color.Black; // Allow the user to set the color of the control border
private RectangleF lastKnownRect; // The last known position of the text
public ScrollingText()
{
// Setup default properties for ScrollingText control
InitializeComponent();
//This turns off internal double buffering of all custom GDI+ drawing
Version v = System.Environment.Version;
if (v.Major < 2)
{
this.SetStyle(ControlStyles.DoubleBuffer, true);
}
else
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
//setup the timer object
timer = new Timer();
timer.Interval = 25; //default timer interval
timer.Enabled = true;
timer.Tick += new EventHandler(Tick);
}
/**////
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
//Make sure our brushes are cleaned up
if (foregroundBrush != null)
foregroundBrush.Dispose();
//Make sure our brushes are cleaned up
if (backgroundBrush != null)
backgroundBrush.Dispose();
//Make sure our timer is cleaned up
if (timer != null)
timer.Dispose();
}
base.Dispose( disposing );
}
Component Designer generated code#region Component Designer generated code
/**////
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
//ScrollingText
this.Name = "ScrollingText";
this.Size = new System.Drawing.Size(216, 40);
this.Click += new System.EventHandler(this.ScrollingText_Click);
}
#endregion
//Controls the animation of the text.
private void Tick(object sender, EventArgs e)
{
//update rectangle to include where to paint for new position
//lastKnownRect.X -= 10;
//lastKnownRect.Width += 20;
lastKnownRect.Inflate(10, 5);
//get the display rectangle
RectangleF refreshRect = lastKnownRect;
refreshRect.X = Math.Max(0, lastKnownRect.X);
refreshRect.Width = Math.Min(lastKnownRect.Width + lastKnownRect.X, this.Width);
refreshRect.Width = Math.Min(this.Width - lastKnownRect.X, refreshRect.Width);
//create region based on updated rectangle
//Region updateRegion = new Region(lastKnownRect);
Region updateRegion = new Region(refreshRect);
//repaint the control
Invalidate(updateRegion);
Update();
}
//Paint the ScrollingTextCtrl.
protected override void OnPaint(PaintEventArgs pe)
{
//Console.WriteLine(pe.ClipRectangle.X + ", " + pe.ClipRectangle.Y + ", " + pe.ClipRectangle.Width + ", " + pe.ClipRectangle.Height);
//Paint the text to its new position
DrawScrollingText(pe.Graphics);
//pass on the graphics obj to the base Control class
base.OnPaint(pe);
}
//Draw the scrolling text on the control
public void DrawScrollingText(Graphics canvas)
{
canvas.SmoothingMode = SmoothingMode.HighQuality;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
//measure the size of the string for placement calculation
SizeF stringSize = canvas.MeasureString(this.text, this.Font);
//Calculate the begining x position of where to paint the text
if (scrollOn)
{
CalcTextPosition(stringSize);
}
//Clear the control with user set BackColor
if (backgroundBrush != null)
{
canvas.FillRectangle(backgroundBrush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}
else
{
canvas.Clear(this.BackColor);
}
// Draw the border
if (showBorder)
{
using (Pen borderPen = new Pen(borderColor))
canvas.DrawRectangle(borderPen, 0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1);
}
// Draw the text string in the bitmap in memory
if (foregroundBrush == null)
{
using (Brush tempForeBrush = new System.Drawing.SolidBrush(this.ForeColor))
canvas.DrawString(this.text, this.Font, tempForeBrush, staticTextPos, yPos);
}
else
{
canvas.DrawString(this.text, this.Font, foregroundBrush, staticTextPos, yPos);
}
lastKnownRect = new RectangleF(staticTextPos, yPos, stringSize.Width, stringSize.Height);
EnableTextLink(lastKnownRect);
}
private void CalcTextPosition(SizeF stringSize)
{
switch (scrollDirection)
{
case ScrollDirection.RightToLeft:
if (staticTextPos this.ClientSize.Width)
staticTextPos = -1 * stringSize.Width;
else
staticTextPos += scrollPixelDistance;
break;
case ScrollDirection.Bouncing:
if (currentDirection == ScrollDirection.RightToLeft)
{
if (staticTextPos this.ClientSize.Width - stringSize.Width)
currentDirection = ScrollDirection.RightToLeft;
else
staticTextPos += scrollPixelDistance;
}
break;
}
//Calculate the vertical position for the scrolling text
switch (verticleTextPosition)
{
case VerticleTextPosition.Top:
yPos = 2;
break;
case VerticleTextPosition.Center:
yPos = (this.ClientSize.Height / 2) - (stringSize.Height / 2);
break;
case VerticleTextPosition.Botom:
yPos = this.ClientSize.Height - stringSize.Height;
break;
}
}
Mouse over, text link logic#region Mouse over, text link logic
private void EnableTextLink(RectangleF textRect)
{
Point curPt = this.PointToClient(Cursor.Position);
//if (curPt.X > textRect.Left && curPt.X textRect.Top && curPt.Y < textRect.Bottom)
if (textRect.Contains(curPt))
{
//Stop the text of the user mouse's over the text
if (stopScrollOnMouseOver)
scrollOn = false;
this.Cursor = Cursors.Hand;
}
else
{
//Make sure the text is scrolling if user's mouse is not over the text
scrollOn = true;
this.Cursor = Cursors.Default;
}
}
private void ScrollingText_Click(object sender, System.EventArgs e)
{
//Trigger the text clicked event if the user clicks while the mouse
//is over the text. This allows the text to act like a hyperlink
if (this.Cursor == Cursors.Hand)
OnTextClicked(this, new EventArgs());
}
public delegate void TextClickEventHandler(object sender, EventArgs args);
public event TextClickEventHandler TextClicked;
private void OnTextClicked(object sender, EventArgs args)
{
//Call the delegate
if (TextClicked != null)
TextClicked(sender, args);
}
#endregion
Properties#region Properties
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("The timer interval that determines how often the control is repainted")
]
public int TextScrollSpeed
{
set
{
timer.Interval = value;
}
get
{
return timer.Interval;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("How many pixels will the text be moved per Paint")
]
public int TextScrollDistance
{
set
{
scrollPixelDistance = value;
}
get
{
return scrollPixelDistance;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("The text that will scroll accros the control")
]
public string ScrollText
{
set
{
text = value;
this.Invalidate();
this.Update();
}
get
{
return text;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("What direction the text will scroll: Left to Right, Right to Left, or Bouncing")
]
public ScrollDirection ScrollDirection
{
set
{
scrollDirection = value;
}
get
{
return scrollDirection;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("The verticle alignment of the text")
]
public VerticleTextPosition VerticleTextPosition
{
set
{
verticleTextPosition = value;
}
get
{
return verticleTextPosition;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("Turns the border on or off")
]
public bool ShowBorder
{
set
{
showBorder = value;
}
get
{
return showBorder;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("The color of the border")
]
public Color BorderColor
{
set
{
borderColor = value;
}
get
{
return borderColor;
}
}
[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("Determines if the text will stop scrolling if the user's mouse moves over the text")
]
public bool StopScrollOnMouseOver
{
set
{
stopScrollOnMouseOver = value;
}
get
{
return stopScrollOnMouseOver;
}
}
[
Browsable(true),
CategoryAttribute("Behavior"),
Description("Indicates whether the control is enabled")
]
new public bool Enabled
{
set
{
timer.Enabled = value;
base.Enabled = value;
}
get
{
return base.Enabled;
}
}
[
Browsable(false)
]
public Brush ForegroundBrush
{
set
{
foregroundBrush = value;
}
get
{
return foregroundBrush;
}
}
[
ReadOnly(true)
]
public Brush BackgroundBrush
{
set
{
backgroundBrush = value;
}
get
{
return backgroundBrush;
}
}
#endregion
}
public enum ScrollDirection
{
RightToLeft,
LeftToRight,
Bouncing
}
public enum VerticleTextPosition
{
Top,
Center,
Botom
}
}
注意事项
如果要调整滚动速度,可以通过设置以下两个属性的值来实现
TextScrollSpeed 和 TextScrollDistance
TextScrollSpeed 其实是设置刷新频率,单位是毫秒,这个值越小,滚动速度越快。但刷新频率越高,CPU占用率越高。
TextScrollDistance 是指每次刷新移动的像素点,这个值越大,速度越快,但如果太大,文字滚动看起来就不是特别连贯。
所以在实际应用中我们需要同时调整这两个值,以找到最佳的平衡点