【问题标题】:Painting label text with a reveal / fade-in effect使用显示/淡入效果绘制标签文本
【发布时间】:2012-03-21 13:25:54
【问题描述】:

我想开发一个标签,以淡入显示类型的方式显示文本。我希望我需要处理这幅画,以便基本上在每次迭代中向右侧添加更多像素。但是,我遇到了障碍,无法让任何动画正常工作。到目前为止,这是我所拥有的:

class RevealLabel : System.Windows.Forms.Label
{
    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        const int GradWidth = 7;
        Rectangle r = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y,
                                    GradWidth, e.ClipRectangle.Height);
        GraphicsPath p = new GraphicsPath();
        using (SolidBrush bs = new SolidBrush(this.ForeColor))
        {
            using (LinearGradientBrush bg = new LinearGradientBrush(new Rectangle(0, 0, GradWidth, e.ClipRectangle.Height), 
                                                                    this.ForeColor, 
                                                                    this.BackColor, 
                                                                    LinearGradientMode.Horizontal))
            {
                for (int i = 0; i < e.ClipRectangle.Width; i += GradWidth)
                {
                    r.X = i;
                    p.AddString(this.Text, this.Font.FontFamily, (int)this.Font.Style, this.Font.Size, 
                                r, StringFormat.GenericDefault);
                    e.Graphics.FillPath(bg, p);
                    e.Graphics.Flush();
                    long TickStop = DateTime.Now.AddSeconds(0.2).Ticks;
                    while (DateTime.Now.Ticks < TickStop)
                    {
                        System.Windows.Forms.Application.DoEvents();
                    }
                    e.Graphics.FillPath(bs, p);
                }
            }
        }
        e.Graphics.Flush();
    }
}

不确定我是否走在正确的轨道上,因为这呈现出的只是一个可怕的混乱。甚至没有逐渐显示到屏幕上的混乱,而是似乎在后台处理所有内容,然后仅使用最终结果更新屏幕。

所以,我的问题有两个:

  1. 渲染像素/矩形区域的正确方法是什么?我将在每次迭代时附加到右侧?

  2. 如何让它在每次绘制时更新到屏幕上,而不是在完成时只更新一次?

(注意:我也尝试在后台线程中绘制,但一直收到 ArgumentException,我认为是因为 Graphics 对象在离开绘制处理程序方法后不久就退出了可用状态。)

【问题讨论】:

    标签: .net winforms c#-4.0


    【解决方案1】:

    尝试使用计时器:

    public class RevealLabel : Label {
      private System.Windows.Forms.Timer revealTimer = new System.Windows.Forms.Timer();
    
      private int paintWidth = 0;
      private int paintIncrement = 7;
    
      public RevealLabel() {
        this.DoubleBuffered = true;
        revealTimer.Interval = 200;
        revealTimer.Tick += new EventHandler(revealTimer_Tick);
      }
    
      void revealTimer_Tick(object sender, EventArgs e) {
        paintWidth += paintIncrement;
    
        if (paintWidth > this.ClientSize.Width) {
          revealTimer.Enabled = false;
        } else {
          this.Invalidate();
        }
      }
    
      protected override void OnPaint(PaintEventArgs e) {
        if (revealTimer.Enabled) {
          e.Graphics.Clear(this.BackColor);
          Rectangle r = new Rectangle(0, 0, paintWidth, this.ClientSize.Height);
          TextRenderer.DrawText(e.Graphics, this.Text, this.Font, r, Color.Black, Color.Empty, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
        } else {
          paintWidth = 0;
          revealTimer.Start();
        }
      }
    }
    

    我可能会继承 Panel 而不是 Label,因为无论如何你都是自己完成所有绘图。

    如果您希望标签始终具有动画效果,请将 this.Invalidate() 调用中的 Tick 事件移到 IF 块之外。

    【讨论】:

    • 谢谢!为什么你推荐Panel 而不是Label?我一开始是这样做的,但后来发现Panel 隐藏了Text 属性,所以我最终只是从Control 继承。 Panel 会给我带来什么吗?
    • @GuthMD 没什么重要的。 Control 可以。 Panels 提供边框。
    • 你的解决方案对我最终得到的结果有什么影响吗?例如,使用Invalidate 并始终在OnPaint 处理程序中绘制会更好吗? TimerThreadPool 更可取吗? TextRenderer 是否比 Graphics 更适合设置剪辑?
    • @GuthMD “更好”只有你能回答。计时器很便宜。我的代码似乎不那么复杂。无论如何,绘画必须在 UI 线程上进行。选择你的毒药,WinForms 在这两种情况下都不能很好地制作动画。
    【解决方案2】:

    在苦苦思索之后,我终于找到了解决方案,在这里:

    public class RevealLabel : System.Windows.Forms.Control
    {
        private const int GradWidth = 5;
        private const int DrawDelay = 20;
        private int lcBackgroundPaintThreadId;
    
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            e.Graphics.Clear(this.BackColor);
            if (this.DesignMode)
            {
                using (SolidBrush bs = new SolidBrush(this.ForeColor))
                {
                    e.Graphics.DrawString(this.Text, this.Font, bs, 0, 0);
                }
            }
            else
            {
                System.Threading.ThreadPool.QueueUserWorkItem(QueuePaintStep, this.ClientRectangle.X);
            }
        }
        private void QueuePaintStep(object state)
        {
            lcBackgroundPaintThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
            System.Threading.Thread.Sleep(DrawDelay);
            if (System.Threading.Thread.CurrentThread.ManagedThreadId == lcBackgroundPaintThreadId)
            {
                int x = (int)state;
                if (x < this.ClientRectangle.Width)
                {
                    Rectangle r = new Rectangle(x, this.ClientRectangle.Y, GradWidth, this.ClientRectangle.Height);
                    if (System.Threading.Thread.CurrentThread.ManagedThreadId != lcBackgroundPaintThreadId) return;
                    using (LinearGradientBrush bg = new LinearGradientBrush(new Rectangle(0, 0, GradWidth, r.Height),
                                                                            this.ForeColor,
                                                                            this.BackColor,
                                                                            LinearGradientMode.Horizontal))
                    {
                        this.Invoke(AsyncPaintCommon, this, bg, r);
                    }
                    System.Threading.Thread.Sleep(DrawDelay);
                    if (System.Threading.Thread.CurrentThread.ManagedThreadId != lcBackgroundPaintThreadId) return;
                    using (SolidBrush bs = new SolidBrush(this.ForeColor))
                    {
                        this.Invoke(AsyncPaintCommon, this, bs, r);
                    }
                    if (System.Threading.Thread.CurrentThread.ManagedThreadId != lcBackgroundPaintThreadId) return;
                    QueuePaintStep(x + GradWidth);
                }
            }
        }
        private delegate void AsyncPaintDelegate(RevealLabel l, Brush b, Rectangle r);
        private static Delegate AsyncPaintCommon = new AsyncPaintDelegate((RevealLabel l, Brush b, Rectangle r) =>
        {
            using (Graphics g = l.CreateGraphics())
            {
                g.SetClip(r);
                g.DrawString(l.Text, l.Font, b, 0, 0);
            }
        });
    }
    

    欢迎任何反馈。

    【讨论】:

      猜你喜欢
      • 2011-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-02
      • 2012-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多