在Windows Forms应用程序开发中,有时我们需要显示和管理SVG(可缩放矢量图形)文件。本文将详细介绍一个功能完整的SVG查看器控件(SvgViewer)的实现,该控件支持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
}
}
SvgViewer控件提供了一个完整的SVG文件查看和管理解决方案,具有以下特点:
通过这个控件,开发者可以轻松地在WinForms应用程序中实现SVG文件的显示和管理功能。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!