WPF 加载和显示 GIF 图片的完整指南

WPF 加载和显示 GIF 图片的完整指南

在 WPF 中加载和显示 GIF 图片需要一些特殊处理,因为 WPF 的 Image 控件默认不支持动画 GIF。

解决方案一:使用 WpfAnimatedGif 库(推荐)

这是最简单且功能最完整的方法。

实现步骤:

  1. 安装 NuGet 包 :

    在 NuGet 包管理器中安装 WpfAnimatedGif:

    复制代码
    Install-Package WpfAnimatedGif
  2. XAML 实现:

    xml 复制代码
    <Window x:Class="GifDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:gif="http://wpfanimatedgif.codeplex.com"
            mc:Ignorable="d"
            Title="GIF 动画演示" Height="450" Width="800">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <!-- 加载 GIF 图片 -->
            <Image x:Name="gifImage" 
                   gif:ImageBehavior.AnimatedSource="Assets/loading.gif"
                   gif:ImageBehavior.RepeatBehavior="Forever"
                   Stretch="Uniform"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
            
            <!-- 控制面板 -->
            <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
                <Button Content="开始" Click="PlayGif" Margin="5" Padding="10,5"/>
                <Button Content="暂停" Click="PauseGif" Margin="5" Padding="10,5"/>
                <Button Content="恢复" Click="ResumeGif" Margin="5" Padding="10,5"/>
                <Button Content="停止" Click="StopGif" Margin="5" Padding="10,5"/>
                
                <ComboBox x:Name="gifSelector" Margin="10,0" Width="150"
                          SelectionChanged="GifSelectionChanged">
                    <ComboBoxItem Content="加载动画" Tag="Assets/loading.gif"/>
                    <ComboBoxItem Content="庆祝动画" Tag="Assets/celebration.gif"/>
                    <ComboBoxItem Content="进度动画" Tag="Assets/progress.gif"/>
                </ComboBox>
            </StackPanel>
        </Grid>
    </Window>
  3. 代码后台:

    csharp 复制代码
    using System.Windows;
    using WpfAnimatedGif;
    
    namespace GifDemo
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                // 设置默认选择
                gifSelector.SelectedIndex = 0;
            }
            
            // 播放 GIF
            private void PlayGif(object sender, RoutedEventArgs e)
            {
                ImageBehavior.SetAnimatedSource(gifImage, new BitmapImage(
                    new Uri("Assets/loading.gif", UriKind.Relative)));
            }
            
            // 暂停 GIF
            private void PauseGif(object sender, RoutedEventArgs e)
            {
                ImageBehavior.GetAnimator(gifImage)?.Pause();
            }
            
            // 恢复播放
            private void ResumeGif(object sender, RoutedEventArgs e)
            {
                ImageBehavior.GetAnimator(gifImage)?.Play();
            }
            
            // 停止 GIF
            private void StopGif(object sender, RoutedEventArgs e)
            {
                ImageBehavior.GetAnimator(gifImage)?.Dispose();
                gifImage.Source = null;
            }
            
            // 切换 GIF
            private void GifSelectionChanged(object sender, RoutedEventArgs e)
            {
                if (gifSelector.SelectedItem is ComboBoxItem item && item.Tag is string gifPath)
                {
                    var source = new BitmapImage(new Uri(gifPath, UriKind.Relative));
                    ImageBehavior.SetAnimatedSource(gifImage, source);
                }
            }
        }
    }

解决方案二:使用自定义 GIF 解码器

如果你不想使用第三方库,可以使用自定义实现:

csharp 复制代码
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Drawing.Imaging;

namespace GifDemo
{
    public partial class MainWindow : Window
    {
        private Bitmap _gifBitmap;
        private BitmapSource[] _gifFrames;
        private int _currentFrame;
        private bool _isPlaying;
        private readonly DispatcherTimer _animationTimer = new DispatcherTimer();

        public MainWindow()
        {
            InitializeComponent();
            _animationTimer.Tick += NextFrame;
        }

        private void LoadGif(string path)
        {
            // 清理现有资源
            StopGif();
            
            // 加载新 GIF
            _gifBitmap = new Bitmap(path);
            
            // 获取帧数
            int frameCount = _gifBitmap.GetFrameCount(FrameDimension.Time);
            _gifFrames = new BitmapSource[frameCount];
            
            // 提取所有帧
            for (int i = 0; i < frameCount; i++)
            {
                _gifBitmap.SelectActiveFrame(FrameDimension.Time, i);
                _gifFrames[i] = ToBitmapSource(_gifBitmap);
            }
            
            // 获取帧延迟
            var frameDelay = GetFrameDelay(_gifBitmap);
            _animationTimer.Interval = TimeSpan.FromMilliseconds(frameDelay);
            
            // 显示第一帧
            _currentFrame = 0;
            gifImage.Source = _gifFrames[0];
            
            // 开始播放
            PlayGif();
        }

        private int GetFrameDelay(Bitmap gif)
        {
            const int PropertyTagFrameDelay = 0x5100;
            
            // 获取帧延迟属性
            var delayProperty = gif.GetPropertyItem(PropertyTagFrameDelay);
            
            // 默认延迟 (100ms)
            if (delayProperty == null) return 100;
            
            // 返回第一帧的延迟(以毫秒为单位)
            return BitConverter.ToInt32(delayProperty.Value, 0) * 10;
        }

        private BitmapSource ToBitmapSource(Bitmap bitmap)
        {
            using (var memory = new MemoryStream())
            {
                bitmap.Save(memory, ImageFormat.Png);
                memory.Position = 0;
                
                var bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = memory;
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();
                bitmapImage.Freeze();
                
                return bitmapImage;
            }
        }

        private void NextFrame(object sender, EventArgs e)
        {
            if (!_isPlaying || _gifFrames == null) return;
            
            _currentFrame = (_currentFrame + 1) % _gifFrames.Length;
            gifImage.Source = _gifFrames[_currentFrame];
        }

        private void PlayGif(object sender = null, RoutedEventArgs e = null)
        {
            _isPlaying = true;
            _animationTimer.Start();
        }

        private void PauseGif(object sender, RoutedEventArgs e)
        {
            _isPlaying = false;
            _animationTimer.Stop();
        }

        private void StopGif(object sender = null, RoutedEventArgs e = null)
        {
            _isPlaying = false;
            _animationTimer.Stop();
            _currentFrame = 0;
            gifImage.Source = _gifFrames?[0];
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            _animationTimer.Stop();
            _gifBitmap?.Dispose();
        }
    }
}

解决方案三:使用 MediaElement(适用于简单 GIF)

对于不需要透明背景的 GIF,可以使用 MediaElement:

xml 复制代码
<Window x:Class="GifDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GIF 动画演示" Height="450" Width="800">
    <Grid>
        <!-- 使用 MediaElement 播放 GIF -->
        <MediaElement x:Name="mediaElement" 
                      Source="Assets/loading.gif"
                      LoadedBehavior="Play"
                      UnloadedBehavior="Stop"
                      Stretch="Uniform"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"/>
        
        <!-- 控制面板 -->
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="10">
            <Button Content="播放" Click="PlayGif" Margin="5" Padding="10,5"/>
            <Button Content="暂停" Click="PauseGif" Margin="5" Padding="10,5"/>
            <Button Content="停止" Click="StopGif" Margin="5" Padding="10,5"/>
        </StackPanel>
    </Grid>
</Window>
csharp 复制代码
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    
    private void PlayGif(object sender, RoutedEventArgs e)
    {
        mediaElement.Play();
    }
    
    private void PauseGif(object sender, RoutedEventArgs e)
    {
        mediaElement.Pause();
    }
    
    private void StopGif(object sender, RoutedEventArgs e)
    {
        mediaElement.Stop();
    }
}

常见问题解决方案

1. GIF 不播放

  • 检查文件路径是否正确
  • 确保 GIF 文件已设置为 "Resource" 或 "Content" 生成操作
  • 验证 GIF 文件是否损坏(用其他软件打开测试)

2. 透明背景显示为黑色

  • 使用支持透明背景的解决方案(如 WpfAnimatedGif)
  • 确保 GIF 本身支持透明
  • 在 Image 控件上设置 Background="Transparent"

3. 性能问题

  • 避免加载过多 GIF

  • 暂停不可见的 GIF

  • 降低 GIF 分辨率

  • 使用 BitmapCache 提高渲染性能:

    xml 复制代码
    <Image.CacheMode>
        <BitmapCache EnableClearType="True" RenderAtScale="1" />
    </Image.CacheMode>

4. 控制 GIF 播放次数

xml 复制代码
<Image gif:ImageBehavior.AnimatedSource="animation.gif"
       gif:ImageBehavior.RepeatBehavior="3x"/> <!-- 播放3次 -->

总结

在 WPF 中加载和显示 GIF 图片有以下几种方法:

  1. 推荐方案 :使用 WpfAnimatedGif NuGet 包

    • 优点:功能完整、支持透明背景、易于使用
    • 缺点:需要添加外部依赖
  2. 自定义解码器:

    • 优点:无外部依赖
    • 缺点:实现复杂、功能有限
  3. 使用 MediaElement:

    • 优点:内置支持
    • 缺点:不支持透明背景、功能有限

对于大多数应用场景,推荐使用 WpfAnimatedGif 库,它提供了最完整的 GIF 支持,包括:

  • 播放控制(播放、暂停、停止)
  • 速度调整
  • 播放次数控制
  • 透明背景支持
  • 事件通知(如帧改变、播放完成等)

通过本文提供的代码示例,您可以轻松地在 WPF 应用中实现 GIF 加载和播放功能,并根据需要添加自定义控制功能。

相关推荐
做个文艺程序员1 天前
MQ第12篇:AI原生消息架构演进:从MQ到事件驱动的全链路设计
架构·消息队列·wpf·事件驱动架构·ai-native·cqrs
donk13143 天前
React Native for OpenHarmony 实战:三方库 react-native-circular-action-menu 的鸿蒙化适配指南
javascript·分布式·react native·react.js·wpf·harmonyos
donk13144 天前
React Native for OpenHarmony 实战:三方库 react-native-step-indicator 的鸿蒙化适配指南
分布式·react native·react.js·wpf·harmonyos
He BianGu4 天前
【笔记】WPF中RenderOptions类的详细功能
笔记·wpf
VidDown4 天前
上线三个月后的第一次事故:一个 Django 服务的日志、监控与故障复盘
数据库·python·django·wpf·音视频·视频编解码
He BianGu4 天前
【笔记】WPF中CachingHint的功能
笔记·wpf
He BianGu4 天前
【笔记】在WPF中BitmapScalingMode的作用
笔记·wpf
Vae_Mars5 天前
幂等性学习笔记
笔记·学习·wpf
samble6 天前
从零搭建灌装监控系统(十九):单元测试实战,xUnit + 内存SQLite + 反射测试私有方法
c#·wpf·mvvm·modbus·工业控制
光泽雨7 天前
路由概念和路由事件 继承链路 RadioButton的事件解析
wpf