编辑
2025-02-14
C# 应用
00
请注意,本文编写于 81 天前,最后修改于 81 天前,其中某些信息可能已经过时。

目录

简介
核心功能
实现详解
总结

简介

在Windows Forms应用程序开发中,有时我们需要显示和管理SVG(可缩放矢量图形)文件。本文将详细介绍一个功能完整的SVG查看器控件(SvgViewer)的实现,该控件支持SVG文件的加载、动画播放、缩放等功能。

核心功能

  • SVG文件加载(单个/批量/目录)
  • 支持从程序集资源加载SVG
  • 动画播放控制(播放/暂停/停止)
  • 帧切换(上一个/下一个)
  • 自动播放和循环播放
  • 多种显示模式
  • 资源缓存机制

实现详解

C#
using Svg; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Timer = System.Windows.Forms.Timer; namespace AppSvg01 { /// <summary> /// SVG显示控件 /// </summary> public class SvgViewer : Control { private readonly Timer animationTimer; private readonly List<SvgDocument> svgDocuments; private readonly Dictionary<string, SvgDocument> resourceCache; private Image currentImage; private int currentIndex; private bool isPlaying; #region Properties private int interval = 1000; // 默认1秒 [Category("Behavior")] [Description("设置SVG切换间隔(毫秒)")] public int Interval { get => interval; set { if (value < 1) value = 1; interval = value; animationTimer.Interval = value; } } [Category("Behavior")] [Description("是否自动播放")] public bool AutoPlay { get; set; } [Category("Behavior")] [Description("是否循环播放")] public bool Loop { get; set; } = true; [Category("Appearance")] [Description("SVG缩放模式")] public PictureBoxSizeMode SizeMode { get; set; } = PictureBoxSizeMode.Zoom; [Browsable(false)] public int CurrentIndex { get => currentIndex; set { if (value >= 0 && value < svgDocuments.Count) { currentIndex = value; UpdateDisplay(); } } } [Browsable(false)] public bool IsPlaying => isPlaying; [Browsable(false)] public int Count => svgDocuments.Count; #endregion #region Events public event EventHandler<int> FrameChanged; public event EventHandler PlaybackCompleted; #endregion public SvgViewer() { SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); svgDocuments = new List<SvgDocument>(); resourceCache = new Dictionary<string, SvgDocument>(); animationTimer = new Timer { Interval = Interval, Enabled = false }; animationTimer.Tick += AnimationTimer_Tick; } #region Public Methods /// <summary> /// 添加SVG文件 /// </summary> public void AddFile(string filePath) { try { if (File.Exists(filePath)) { var doc = SvgDocument.Open(filePath); svgDocuments.Add(doc); if (svgDocuments.Count == 1) { UpdateDisplay(); if (AutoPlay) Play(); } } } catch (Exception ex) { throw new Exception($"Failed to load SVG file: {filePath}", ex); } } /// <summary> /// 添加多个SVG文件 /// </summary> public void AddFiles(string[] filePaths) { foreach (var path in filePaths) { AddFile(path); } } /// <summary> /// 从目录加载SVG文件 /// </summary> public void LoadFromDirectory(string directoryPath, string searchPattern = "*.svg") { if (!Directory.Exists(directoryPath)) throw new DirectoryNotFoundException(directoryPath); var files = Directory.GetFiles(directoryPath, searchPattern) .OrderBy(x => x) .ToArray(); AddFiles(files); } /// <summary> /// 添加嵌入资源SVG /// </summary> public void AddResource(string resourceName, Assembly assembly = null) { assembly ??= Assembly.GetExecutingAssembly(); if (resourceCache.ContainsKey(resourceName)) { svgDocuments.Add(resourceCache[resourceName]); return; } try { using (var stream = assembly.GetManifestResourceStream(resourceName)) { if (stream == null) throw new Exception($"Resource not found: {resourceName}"); var doc = SvgDocument.Open<SvgDocument>(stream); resourceCache[resourceName] = doc; svgDocuments.Add(doc); if (svgDocuments.Count == 1) { UpdateDisplay(); if (AutoPlay) Play(); } } } catch (Exception ex) { throw new Exception($"Failed to load SVG resource: {resourceName}", ex); } } /// <summary> /// 清除所有SVG /// </summary> public void Clear() { Stop(); currentImage?.Dispose(); currentImage = null; svgDocuments.Clear(); currentIndex = 0; Invalidate(); } /// <summary> /// 开始播放 /// </summary> public void Play() { if (svgDocuments.Count > 1 && !isPlaying) { isPlaying = true; animationTimer.Start(); } } /// <summary> /// 暂停播放 /// </summary> public void Pause() { isPlaying = false; animationTimer.Stop(); } /// <summary> /// 停止播放 /// </summary> public void Stop() { isPlaying = false; animationTimer.Stop(); currentIndex = 0; UpdateDisplay(); } /// <summary> /// 下一个SVG /// </summary> public void Next() { if (svgDocuments.Count == 0) return; currentIndex = (currentIndex + 1) % svgDocuments.Count; UpdateDisplay(); } /// <summary> /// 上一个SVG /// </summary> public void Previous() { if (svgDocuments.Count == 0) return; currentIndex = (currentIndex - 1 + svgDocuments.Count) % svgDocuments.Count; UpdateDisplay(); } #endregion #region Protected Methods protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (currentImage == null) return; var rect = GetDisplayRectangle(); e.Graphics.DrawImage(currentImage, rect); } protected override void Dispose(bool disposing) { if (disposing) { animationTimer.Dispose(); currentImage?.Dispose(); svgDocuments.Clear(); resourceCache.Clear(); } base.Dispose(disposing); } #endregion #region Private Methods private void AnimationTimer_Tick(object sender, EventArgs e) { if (!isPlaying || svgDocuments.Count <= 1) return; currentIndex++; if (currentIndex >= svgDocuments.Count) { if (Loop) { currentIndex = 0; } else { Stop(); PlaybackCompleted?.Invoke(this, EventArgs.Empty); return; } } UpdateDisplay(); } private void UpdateDisplay() { if (svgDocuments.Count == 0 || currentIndex >= svgDocuments.Count) return; var oldImage = currentImage; var doc = svgDocuments[currentIndex]; // 根据控件大小计算图像尺寸 var rect = GetDisplayRectangle(); currentImage = doc.Draw(rect.Width, rect.Height); oldImage?.Dispose(); Invalidate(); FrameChanged?.Invoke(this, currentIndex); } private Rectangle GetDisplayRectangle() { if (currentImage == null || svgDocuments.Count == 0) return ClientRectangle; var rect = ClientRectangle; var doc = svgDocuments[currentIndex]; var aspectRatio = doc.Width / (float)doc.Height; switch (SizeMode) { case PictureBoxSizeMode.Normal: return new Rectangle(0, 0, (int)doc.Width, (int)doc.Height); case PictureBoxSizeMode.StretchImage: return rect; case PictureBoxSizeMode.AutoSize: return new Rectangle(0, 0, (int)doc.Width, (int)doc.Height); case PictureBoxSizeMode.CenterImage: return new Rectangle( (rect.Width - (int)doc.Width) / 2, (rect.Height - (int)doc.Height) / 2, (int)doc.Width, (int)doc.Height); case PictureBoxSizeMode.Zoom: var scale = Math.Min( rect.Width / (float)doc.Width, rect.Height / (float)doc.Height); var width = (int)(doc.Width * scale); var height = (int)(doc.Height * scale); return new Rectangle( (rect.Width - width) / 2, (rect.Height - height) / 2, width, height); default: return rect; } } #endregion } }

image.png

总结

SvgViewer控件提供了一个完整的SVG文件查看和管理解决方案,具有以下特点:

  1. 易于集成到现有项目
  2. 功能完善,包含基础和高级特性
  3. 性能优化,支持流畅的动画效果
  4. 异常处理机制完善
  5. 灵活的显示模式

通过这个控件,开发者可以轻松地在WinForms应用程序中实现SVG文件的显示和管理功能。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!