eaglet

本博专注于基于微软技术的搜索相关技术
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Winform 下无闪烁走马灯效果实现

Posted on 2008-04-03 12:02  eaglet  阅读(24107)  评论(11编辑  收藏  举报
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(105);

            
//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(105);

            
//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>
    
/// Summary description for ScrollingTextControl.
    
/// </summary>

    [
    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);            
        }


        
/// <summary>
        
/// Clean up any resources being used.
        
/// </summary>

        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
    
        
//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(105);

            
//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, 
00this.ClientSize.Width, this.ClientSize.Height);
            }

            
else
            
{
                canvas.Clear(
this.BackColor);
            }


            
// Draw the border
            if (showBorder)
            
{
                
using (Pen borderPen = new Pen(borderColor))
                    canvas.DrawRectangle(borderPen, 
00this.ClientSize.Width - 1this.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 < (-1 * (stringSize.Width)))
                        staticTextPos 
= this.ClientSize.Width - 1;
                    
else
                        staticTextPos 
-= scrollPixelDistance;
                    
break;
                
case ScrollDirection.LeftToRight:
                    
if (staticTextPos > this.ClientSize.Width)
                        staticTextPos 
= -1 * stringSize.Width;
                    
else
                        staticTextPos 
+= scrollPixelDistance;
                    
break;
                
case ScrollDirection.Bouncing:
                    
if (currentDirection == ScrollDirection.RightToLeft)
                    
{
                        
if (staticTextPos < 0)
                            currentDirection 
= ScrollDirection.LeftToRight;
                        
else
                            staticTextPos 
-= scrollPixelDistance;                        
                    }

                    
else if (currentDirection == ScrollDirection.LeftToRight)
                    
{
                        
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
        

        
Properties        
    }


    
public enum ScrollDirection
    
{
        RightToLeft,
        LeftToRight,
        Bouncing
    }


    
public enum VerticleTextPosition
    
{
        Top,
        Center,
        Botom
    }

}


注意事项
如果要调整滚动速度,可以通过设置以下两个属性的值来实现
TextScrollSpeed 和 TextScrollDistance
TextScrollSpeed 其实是设置刷新频率,单位是毫秒,这个值越小,滚动速度越快。但刷新频率越高,CPU占用率越高。
TextScrollDistance 是指每次刷新移动的像素点,这个值越大,速度越快,但如果太大,文字滚动看起来就不是特别连贯。
所以在实际应用中我们需要同时调整这两个值,以找到最佳的平衡点