WPF默认不支持GIF动画播放,Image控件仅显示首帧;需用MediaElement(支持pack://路径)、自定义DispatcherTimer控制帧切换,或谨慎使用WpfAnimatedGif库。

WPF 默认不支持 GifBitmapDecoder 播放动画
直接用 Image 控件加载 GIF 资源,只会显示第一帧——这不是你代码写错了,是 WPF 的 BitmapImage 和默认资源加载机制压根不触发帧切换逻辑。它把 GIF 当静态图解码了。
常见错误现象:Image.Source = new BitmapImage(new Uri("pack://application:,,,/res.gif")) 看起来没报错,但动图静止;或者用 GifBitmapDecoder 手动解码后赋值给 Image.Source,依然不动。
- 根本原因:WPF 的渲染管线不主动轮询 GIF 帧,也没有内置定时器驱动播放
- 必须自己接管帧序列 + 时间调度,不能依赖控件自动行为
-
GifBitmapDecoder.Frames可读,但每个BitmapFrame是独立对象,不含延时信息,得从 GIF 元数据里手动提取
用 MediaElement 加载本地 GIF 文件最省事
如果 GIF 存在本地文件系统或 pack:// 资源路径下,且不需要透明通道精确控制(比如叠加在其他 UI 上),MediaElement 是最快能跑通的方案。
使用场景:启动页、状态提示、非交互式背景动效
- 仅支持完整路径,不支持嵌入资源流(
StreamResourceInfo不行) - 需设置
LoadedBehavior="Play"和UnloadedBehavior="Stop",否则卸载页面后音频/计时器可能残留 - 透明背景在某些 Windows 版本上会变黑,可加
OpacityMask补救,但增加渲染开销
<MediaElement Source="pack://application:,,,/Resources/loading.gif"
LoadedBehavior="Play"
UnloadedBehavior="Stop"
Width="64" Height="64"/>
自定义控件 + DispatcherTimer 精确控制帧率
需要逐帧控制、暂停/跳帧、与业务逻辑联动(比如加载完成时停在最后一帧),就得自己写播放器。核心是读取每帧延迟时间,并用 DispatcherTimer 触发 Image.Source 切换。
关键点:
- 用
GifBitmapDecoder解码后,遍历Frames,通过反射读取Delay属性(WPF 未公开,需访问BitmapMetadata中的"/grctlext/Delay") - 不要用
Thread.Sleep或Task.Delay,必须用DispatcherTimer确保 UI 线程安全 - 每帧切换前调用
GC.Collect()不必要,但应复用BitmapFrame实例,避免频繁解码同一帧
示例片段(获取首帧延迟):
var decoder = new GifBitmapDecoder(..., BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
var frame = decoder.Frames[0];
var metadata = frame.Metadata as BitmapMetadata;
var delay = (ushort?)metadata?.GetQuery("/grctlext/Delay") ?? 100;
第三方库 WpfAnimatedGif 的坑要提前踩
多数人搜到这个 NuGet 包,但它不是“开箱即用”:默认行为在 .NET 6+ WPF 项目中容易卡死或内存泄漏,尤其配合 VisualBrush 或模板复用时。
兼容性影响:
- 必须显式声明
GifImage.GifSource附加属性,不能只设Source - 若 GIF 路径含空格或中文,
pack://协议会解析失败,得先转为绝对 URI 再传入 - 在
ItemsControl模板里大量使用时,帧定时器未按控件生命周期销毁,导致后台持续占用 CPU
建议做法:只在单个、生命周期明确的控件中使用;替换为自定义方案前,先加 try/catch 捕获 InvalidOperationException(常因跨线程访问引发)
真正麻烦的从来不是“怎么播”,而是“播完要不要重置”“缩放时会不会丢帧”“主题色变化时透明通道是否错位”——这些细节不会报错,但会在某个 DPI 缩放比例或夜间模式下突然暴露。










