C# Winform中绘制动画的方法
最近在做一个图片查看器,由于使用一般的PctureBox,在性能和缩放控制上都无法满足预期的要求,因此所有组件的呈现均是通过重写控件的OnPaint事件来绘制。在查看gif图片时发现Graphics.DrawImage只呈现第一帧,无法满足预期要求,因此经过摸索寻找到了解决自绘gif的较好办法。
这里介绍一个.net自身携带的类ImageAnimator,这个类类似于控制动画的时间轴,使用ImageAnimator.CanAnimate可以判断一个图片是否为动画,调用ImageAnimator.Animate可以开始播放动画,即每经过一帧的时间触发一次OnFrameChanged委托,我们只要在该委托中将Image的活动帧选至下一帧再迫使界面重绘就可以实现动画效果了。
为了方便以后的使用,我将这些代码整合到了一起,形成一个AnimateImage类,该类提供了CanAnimate、FrameCount、CurrentFrame等属性,以及Play()、Stop()、Reset()等动画常用的方法,代码如下
1
using System; 2
using System.Collections.Generic; 3
using System.Text; 4
using System.Drawing; 5
using System.Drawing.Imaging; 6
7
namespace GifTest 8


{ 9

/**//// <summary> 10
/// 表示一类带动画功能的图像。 11
/// </summary> 12
public class AnimateImage 13

{ 14
Image image; 15
FrameDimension frameDimension; 16

/**//// <summary> 17
/// 动画当前帧发生改变时触发。 18
/// </summary> 19
public event EventHandler<EventArgs> OnFrameChanged; 20
21

/**//// <summary> 22
/// 实例化一个AnimateImage。 23
/// </summary> 24
/// <param name="img">动画图片。</param> 25
public AnimateImage(Image img) 26

{ 27
image = img; 28
29
lock (image) 30

{ 31
mCanAnimate = ImageAnimator.CanAnimate(image); 32
if (mCanAnimate) 33

{ 34
Guid[] guid = image.FrameDimensionsList; 35
frameDimension = new FrameDimension(guid[0]); 36
mFrameCount = image.GetFrameCount(frameDimension); 37
} 38
} 39
} 40
41
bool mCanAnimate; 42
int mFrameCount = 1, mCurrentFrame = 0; 43
44

/**//// <summary> 45
/// 图片。 46
/// </summary> 47
public Image Image 48

{ 49

get
{ return image; } 50
} 51
52

/**//// <summary> 53
/// 是否动画。 54
/// </summary> 55
public bool CanAnimate 56

{ 57

get
{ return mCanAnimate; } 58
} 59
60

/**//// <summary> 61
/// 总帧数。 62
/// </summary> 63
public int FrameCount 64

{ 65

get
{ return mFrameCount; } 66
} 67
68

/**//// <summary> 69
/// 播放的当前帧。 70
/// </summary> 71
public int CurrentFrame 72

{ 73

get
{ return mCurrentFrame; } 74
} 75
76

/**//// <summary> 77
/// 播放这个动画。 78
/// </summary> 79
public void Play() 80

{ 81
if (mCanAnimate) 82

{ 83
lock (image) 84

{ 85
ImageAnimator.Animate(image, new EventHandler(FrameChanged)); 86
} 87
} 88
} 89
90

/**//// <summary> 91
/// 停止播放。 92
/// </summary> 93
public void Stop() 94

{ 95
if (mCanAnimate) 96

{ 97
lock (image) 98

{ 99
ImageAnimator.StopAnimate(image, new EventHandler(FrameChanged)); 100
} 101
} 102
} 103
104

/**//// <summary> 105
/// 重置动画,使之停止在第0帧位置上。 106
/// </summary> 107
public void Reset() 108

{ 109
if (mCanAnimate) 110

{ 111
ImageAnimator.StopAnimate(image, new EventHandler(FrameChanged)); 112
lock (image) 113

{ 114
image.SelectActiveFrame(frameDimension, 0); 115
mCurrentFrame = 0; 116
} 117
} 118
} 119
120
private void FrameChanged(object sender, EventArgs e) 121

{ 122
mCurrentFrame = mCurrentFrame + 1 >= mFrameCount ? 0 : mCurrentFrame + 1; 123
lock (image) 124

{ 125
image.SelectActiveFrame(frameDimension, mCurrentFrame); 126
} 127
if (OnFrameChanged != null) 128

{ 129
OnFrameChanged(image, e); 130
} 131
} 132
} 133
} 134

使用如下方法调用:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;
namespace GifTest
{
public partial class Form1 : Form
{
AnimateImage image;
public Form1()
{
InitializeComponent();
image = new AnimateImage(Image.FromFile(@"C:\Documents and Settings\Administrator\My Documents\My Pictures\未命名.gif"));
image.OnFrameChanged += new EventHandler<EventArgs>(image_OnFrameChanged);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
void image_OnFrameChanged(object sender, EventArgs e)
{
Invalidate();
}
private void Form1_Load(object sender, EventArgs e)
{
image.Play();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
lock (image.Image)
{
e.Graphics.DrawImage(image.Image, new Point(0, 0));
}
}
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text.Equals("Stop"))
{
image.Stop();
button1.Text = "Play";
}
else
{
image.Play();
button1.Text = "Stop";
}
Invalidate();
}
private void button2_Click(object sender, EventArgs e)
{
image.Reset();
button1.Text = "Play";
Invalidate();
}
}
}
有点不完美的地方,在Paint事件中,必须锁定Image,否则很容易出现“对象当前正在其他地方使用。”的异常,因为AnimateImage也在使用这个Image对象。如果你有更好的解决办法,欢迎给我留言~~
浙公网安备 33010602011771号