【问题标题】:Winforms smooth animationsWinforms 平滑动画
【发布时间】:2020-07-02 17:07:19
【问题描述】:

所以我尝试用Winforms 制作一些动画,更具体地说,是从左到右的动画。但是,我遇到了多个问题。 首先,System.Windows.Forms.Timer 甚至像System.Threading.Timer 这样的其他 Timer 类对于我想要的动画来说还不够快。作为补偿,我可以增加添加到左右动画的像素数量。但是,这会导致动画不连贯,这不是我想要的。为了解决这个问题,我使用了自己的计时器(在另一个线程上),它更准确:

long frequency = Stopwatch.Frequency;
long prevTicks = 0;
while (true)
{
    double interval = ((double)frequency) / Interval;
    long ticks = Stopwatch.GetTimestamp();
    if (ticks >= prevTicks + interval)
    {
        prevTicks = ticks;
        Tick?.Invoke(this, EventArgs.Empty);
    }
}

然而,这有其自身的缺点。首先,这会给 CPU 带来沉重的负担。其次,如果我想一次将左右动画增加 1 个像素以获得流畅的动画,我无法以足够快的速度重绘。解决这个问题的方法是直接在CreateGraphics提供的图形上绘制,效果还不错,除了我们去透明画笔的时候。然后,事情变慢了。 所有这一切的解决方案就是增加我在左右动画上一次绘制的像素数量,但这会导致动画缺乏平滑度。下面是一些测试代码:

private int index;
private Graphics g;
private Brush brush;
private void FastTimer_Tick(object sender, EventArgs e)
{
    index++;
    if (g == null)
    {
        g = CreateGraphics();
    }
    if (brush == null)
    {
        brush = new SolidBrush(Color.FromArgb(120, Color.Black));
    }
    g.FillRectangle(brush, index, 0, 1, Height);
}

我听说GDI 更快,因为它是硬件加速的,但我不知道如何使用它。 有人在坚持winforms 的同时有解决方案吗?谢谢。

编辑:这是一个示例视频: https://www.youtube.com/watch?v=gcOttFFCUz8&feature=youtu.be 当窗体最小化时,它非常平滑。但是,当表格最大化时,我必须补偿平滑度以提高速度。我想知道如何更快地重绘(可能使用 GDI),以便我仍然可以使用 +1px 动画,以获得流畅的体验。

【问题讨论】:

  • 使用双缓冲控件作为 PictureBox 并在其 Paint 事件(或 OnPaint 方法)中绘制您的形状。不要使用CreateGraphics()(另外,检查null 是无关紧要的)。抗锯齿可以隐藏单步运动,但您应该使用浮点值来增加水平移位。看起来您想绘制一条移动线(如在声波计/分析仪中?)。然后,您可以使用 StopWatch 或 higher resolution timer 来生成这些值。您必须测试 UI 对此的反应。
  • 感谢吉米的回复!我使用CreateGraphics 绘图,因为如果我不这样做,那么调用 Refresh 或 Invalidate 太慢了。另外,我正在尝试绘制一个与控件宽度和高度相同的移动矩形(大约是屏幕的大小)。
  • 如果你在它的 Paint 事件中 Invalidate() 你的 Control ,那么只有当你的 Timer 说它是时间时才绘制。要比这更快地绘制,您需要另一个 Engine。 -- CreateGraphics() 并没有更快:您实际上创建了两次该对象,因为它是在 Control 收到 WM_PAINT 消息时创建的。如果您尝试重用 store 对象,您将得到非常奇怪的结果(或完全错误,因为剪辑)或异常。 -- 这个:FillRectangle(brush, index, 0, 1, Height); 没有画出你描述的内容。
  • 除非你认为你可以让这条线坚持。你会失望的。
  • 我已经尝试过您的解决方案,但是,它实际上并没有我在那里发布的代码那么快。此外,上面的代码确实产生了我想要的(只是不够快),因为在CreateGraphics 提供的对象上绘图不会使控件无效,所以那里的一切都保持原样。

标签: c# .net winforms animation gdi+


【解决方案1】:

您的代码有很多需要改进/更改的地方,
这里有个建议,请看代码里面的cmets:

private void button1_Click(object sender, EventArgs e)
{
    // optional: most animations are made in another thread to keep the GUI free, please see Invoke() to update GUI in StartAnimation()
    System.Threading.Thread t1 = new System.Threading.Thread(StartAnimation);
    t1.Start();
}
    
private void StartAnimation()
{
    // Use a stop watch
    if (st == null)
    {
        st = new Stopwatch();
        st.Start();
    }

    long prevTicks = 0;
    // a bool field to control the start and stop of the animation is better practice the While(true)
    while (IsAnimationActive)
    {
        double interval = 100000;
        long ticks = st.ElapsedTicks;

        if (ticks >= prevTicks + interval)
        {
            prevTicks = ticks;
            // Execture animation using invoke to prevent cross threading exception while updating the gui 
            pictureBox1.Invoke(new MethodInvoker(() =>
            {
                pictureBox1.Refresh();
            }));
        }
    }

    st.Stop();
}

private int index;
private int Height = 5;
// Use the Paint event of the control and not CreateGraphics()
// note: picture box is a control that is most suitable for animations and graphics
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    if (index >= pictureBox1.Width)
        index = 0;

    index++;
    Graphics g = e.Graphics;
    // set HighQuality  fot the SmoothingMode property
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.FillRectangle(Brushes.Black, 0, 0, index, Height);
}

【讨论】:

  • // 最好在本地声明brish 为什么?你为什么要泄露它?
  • @TaW 你说得对,我的意思是别的,忘记更改 - 请查看编辑(将静态内置黑色画笔传递给 FillRectangle())。
  • 感谢您的建议。但是,我的原始代码中的大多数(如果不是全部)(并非全部显示)都执行此处显示的操作。此外,线不应该移动,矩形应该保持在 X = 0 并扩大宽度。
  • 但是这个重载的第二个参数是矩形的 X 点,你正在传递和推进index。反正注意这一行g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;我也把index移到width参数了
  • 永远不要使用control.CreateGraphics!永远不要尝试缓存 Graphics 对象!使用Graphics g = Graphics.FromImage(bmp) 或在控件的Paint 事件中使用e.Graphics 参数绘制到Bitmap bmp 中..
【解决方案2】:

如果您希望它尽可能快地运行,是否将该代码弹出到包含在 async/await 方法中的循环中?

private void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    SweepRight(Color.Black);
    button1.Enabled = true;
}

private async void SweepRight(Color c)
{
    using (Graphics g = CreateGraphics())
    {
        using (SolidBrush brush = new SolidBrush(Color.FromArgb(120, c)))
        {
            await Task.Run(() =>
            {
                for (int i = 0; i <= Width; i++)
                {
                    g.FillRectangle(brush, i, 0, 1, Height);
                }
            });
        }
    }
}

【讨论】:

  • 这和原始代码一样工作,但它仍然受到图形填充速度的限制。当没有透明度时,它的效果很好,但有了它,它的速度就会大大减慢。
  • 是的,我认为你必须尝试一种完全不同的方法。
猜你喜欢
  • 2017-03-12
  • 2012-09-17
  • 1970-01-01
  • 1970-01-01
  • 2019-08-24
  • 2023-03-04
  • 2020-07-23
  • 2018-04-28
  • 2017-03-29
相关资源
最近更新 更多