循环使用一组图像并使用DrawImageUnscaled

时间:2015-11-06 19:27:54

标签: c# image animation for-loop

我正在开发一个Windows窗体应用程序,它由屏幕上的单个图像和加载时称为动画的按钮组成。单击按钮时,它应循环显示10个图像(保存在文件夹中为" 1.png"," 2.png" ..." 10.png&# 34;)并一个接一个地显示它们,创建一个动画效果。

但是,当我点击动画按钮时,它没有做任何事情。但是,如果我在单击动画按钮后单击屏幕,它将显示保存在目录中的最后一个图像(" 10.png")。

这是我对Form1_Paint()方法的代码:这是在表单加载时应用的。

pictureBit是原始图片的名称)

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics windowG = e.Graphics;

    if (animate)
    {
        for (int i = 1; i <= 10; i++)
        {
            Bitmap images = (Bitmap)Image.FromFile(i+".png");
            windowG.DrawImageUnscaled(images, 0, 0);
        }
    }
    else
    {
        windowG.DrawImageUnscaled(pictureBit, 0, 0);
    }
}

单击该按钮时,它会将布尔值设置为true:

private void start_animate(object sender, EventArgs e)
{
    animate = true;
} 

解决此问题的最佳方法是什么?

2 个答案:

答案 0 :(得分:2)

代码中的问题是您在Paint事件处理程序内循环。最好的情况是,这会导致程序的其余部分在图像动画时冻结,最坏的情况(如在您的场景中),您会发现由于Windows处理屏幕更新的方式,屏幕上的图像仅更新处理程序返回后,阻止您完全看到动画。

对于简单的动画,例如您正在做的事情,最简单的方法是更新计时器上的Bitmap并使表单无效,并让Paint事件处理程序仅绘制一度。例如:

private Bitmap _currentImage;

private void Form1_Paint(object sender, PaintEventArgs e)
{            
    if (animate)
    {                            
        e.Graphics.DrawImageUnscaled(_currentImage, 0, 0);
    }
    else
    {
        e.Graphics.DrawImageUnscaled(pictureBit, 0, 0);
    }

}

private async void start_animate(object sender, EventArgs e)
{
    animate = true;
    int imageIndex = 1;

    while (animate)
    {
        _currentImage = (Bitmap)Image.FromFile(imageIndex  + ".png");
        Invalidate();

        await Task.Delay(100); // wait 100 milliseconds

        imageIndex++;
        if (imageIndex > 10)
        {
            imageIndex = 1;
        }
    }
}

在这里使用asyncawait允许您拥有看似普通循环的代码,但允许该方法返回并让线程在每次失效之间继续处理(即{执行{1}}。

请注意,上面的重点是尝试在固定的时间间隔内为图像设置动画。无法保证在更新图像时实际重绘表单的频率。通常对于动画来说,这仍然是可取的(即您可能会错过一些帧,但整体动画以预期的速率发生)。但是,如果需要,您可以协调动画循环和await Task.Delay(100)事件处理程序,以便动画循环只有在发出事件处理程序已更新表单的信号后才会进入下一帧。

另请注意,上面的“固定间隔”没有考虑处理文件I / O和索引的任何开销。通过使用Paint来跟踪开销所花费的时间并从循环中的延迟中减去该开销,可以实现更精确的时序。由于您的原始代码没有尝试在特定的时间间隔内安排动画,我认为上面的代码示例不需要比现在更精确。 :)


警告:由于您没有提供可靠地重现问题的a good, minimal, complete code example,因此从头开始创建整个程序以验证建议的解决方案是不切实际的。以上只是浏览器代码;我已经编辑过一次来修复我发现审查它的语法和逻辑问题,但我不能保证没有更多。 :)我认为基本的想法足以让你适应自己的需要。

答案 1 :(得分:0)

是的,那不会像你想象的那样发挥作用。 Paint的结果不会转发到设备(屏幕),直到方法返回后的一段时间;这允许窗口系统进行一些优化。因此,在将任何内容发送到屏幕之前,您的Paint方法基本上会绘制所有十个图像

在您单击表单之前,您没有看到任何内容的原因是因为仅在表单认为需要重绘时才调用Paint。简单地更改控件的内容可能不会导致这种情况发生。看一下Control::Invalidate()方法。

您可能会考虑使用System::Windows::Forms::Timer定期更新图像并使控件无效(如果需要)。这使得它保留在Forms线程上,并且还允许您对图像更改的速率进行一些控制。点击按钮可以启动计时器。

我还建议您提前将所有图像加载到内存中,而不是在您打算显示它时从文件中读取每个图像。