OxyPlot 导出图片及 WPF 元素导出为图片的方法

  • A+
所属分类:.NET技术
摘要

独立观察员 2022 年 2 月 26 日 最近有个需求,就是将 OxyPlot 图形导出图片。经过尝试,本文记录三种方法:1、OxyPlot 自带导出方法;2、网上找的导出 WPF 界面元素的方法;3、基于方法 2 的附加属性调用方式。下面将逐一介绍。


OxyPlot 导出图片及 WPF 元素导出为图片的方法

独立观察员 2022 年 2 月 26 日

 

最近有个需求,就是将 OxyPlot 图形导出图片。经过尝试,本文记录三种方法:1、OxyPlot 自带导出方法;2、网上找的导出 WPF 界面元素的方法;3、基于方法 2 的附加属性调用方式。下面将逐一介绍。

 

一、OxyPlot 自带导出方法

同事说这个用 OxyPlot 官方提供的导出方法即可,我在 Demo 中试了一下,是可以的,代码如下:

/// <summary> /// 曲线数据源(OxyPlot) /// </summary> public PlotModel PlotModel { get; set; } = new PlotModel();  ExportPngCmd ??= new RelayCommand(o => true, async o => {     var pngExporter = new PngExporter { Width = (int)PlotModel.Width, Height = (int)PlotModel.Height, };     //string exportPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Export");     string exportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Export");     if (!Directory.Exists(exportPath))     {         Directory.CreateDirectory(exportPath);     }      pngExporter.ExportToFile(PlotModel, Path.Combine(exportPath, $"{DateTime.Now:yyyyMMdd_HHmmss}.png"));     await ConfirmBoxHelper.ShowMessage(DialogVm, "导出完成", 3); });

 

各种导出方法可以在 OxyPlot 官方文档(https://oxyplot.readthedocs.io/en/latest/export/index.html)中查看

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

这里用到的是导出到 PNG 文件的方法,不过用的 NuGet 包最新版(2.1.0)中,PngExporter 中并没有 Background 属性:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

所以如果图表没有设置背景色的话,导出背景为透明的,可以设置上:

PlotModel.Background = OxyColor.Parse("#FFFFFF");

 

总的来说,这个方法简单快捷,而且对 MVVM 友好。不过也有缺点,就是如果有些元素(比如说标题、坐标轴文字)不是使用 OxyPlot 图表控件来生成的话,则导出的图片就不会包含它们了:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

我在实际项目中确实遇到了这个问题,所以需要寻找其它方法,我们接着看。

 

二、导出 WPF 界面元素的方法

首先给出能够导出任意 WPF 界面元素(FrameworkElement)为图片的方法,来源于网络,地址在方法注释中已给出,略作修改,代码如下:

using System; using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging;  namespace WPFTemplateLib.WpfHelpers {     /// <summary>     /// 导出图片帮助类     /// </summary>     public class ExportPicHelper     {         /// <summary>         /// 保存为图片         /// (修改自:https://blog.csdn.net/dhl11/article/details/108621634)         /// </summary>         /// <param name="frameworkElement">可视化元素,可以是Grid、StackPanel等类型的所有可视化元素</param>         /// <param name="filePath">文件路径</param>         /// <param name="errorMsg">错误消息</param>         /// <returns>是否成功</returns>         public static bool SaveToImage(FrameworkElement frameworkElement, string filePath, out string errorMsg)         {             try             {                 errorMsg = string.Empty;                 FileStream fs = new FileStream(filePath, FileMode.Create);                 RenderTargetBitmap bmp = new RenderTargetBitmap(                     (int)frameworkElement.ActualWidth,                      (int)frameworkElement.ActualHeight,                      1 / 96, 1 / 96, PixelFormats.Default);                  bmp.Render(frameworkElement);                 BitmapEncoder encoder = new TiffBitmapEncoder();                 encoder.Frames.Add(BitmapFrame.Create(bmp));                 encoder.Save(fs);                 fs.Close();                 return true;             }             catch (Exception ex)             {                 Console.WriteLine($"保存图片异常:{ex}");                 errorMsg = ex.Message;                 return false;             }         } 	} }

 

用这个方法首先要给界面元素起个名字,我这里给图表区用户控件元素起了个 “Plot” 名称:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

这样在后台代码中就可以用来导出了:

private void ExportPicBtn_OnClick(object sender, RoutedEventArgs e) {     ExportPicture(Plot); }  /// <summary> /// 导出图片 /// </summary> /// <param name="element">xaml里面的某个可视化元素对象</param> private void ExportPicture(FrameworkElement element) {     SaveFileDialog saveFileDialog = new SaveFileDialog     {         Filter = "PNG文件(*.png)|*.png|JPG文件(*.jpg)|*.jpg|BMP文件(*.bmp)|*.bmp|GIF文件(*.gif)|*.gif|TIF文件(*.tif)|*.tif"     };      if (saveFileDialog.ShowDialog() == true)     {         string dir = System.IO.Path.GetDirectoryName(saveFileDialog.FileName);         if (!Directory.Exists(dir))         {             Directory.CreateDirectory(dir);         }          string filePath = saveFileDialog.FileName;         if (File.Exists(filePath))         {             File.Delete(filePath);         }          bool success = ExportPicHelper.SaveToImage(element, filePath, out string errorMsg);         if (success)         {             MessageBox.Show($"导出成功");         }         else         {             MessageBox.Show($"导出失败 {errorMsg}");         }     } }

 

可以看到想要导出的内容都导出成功了:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

优点是显而易见的,缺点就是导出逻辑要写在后台代码中,对 MVVM 模式不友好。下面来看看本人修改的使用附加属性的方案,尝试解决这个问题。

 

三、通过附加属性来使用

还是先给出代码:

using System; using System.IO; using System.Windows; using WPFTemplateLib.WpfHelpers;  namespace WPFTemplateLib.Attached {     /// <summary>     /// 导出图片附加属性类     /// </summary>     public class ExportPicAttached : DependencyObject     {         #region 是否开始导出          public static bool GetIsExporting(DependencyObject obj)         {             return (bool)obj.GetValue(IsExportingProperty);         }          public static void SetIsExporting(DependencyObject obj, bool value)         {             obj.SetValue(IsExportingProperty, value);         }          /// <summary>         /// 是否正在导出(运行时设置为 true 则将附加的元素导出为图片)         /// </summary>         public static readonly DependencyProperty IsExportingProperty =             DependencyProperty.RegisterAttached("IsExporting", typeof(bool), typeof(ExportPicAttached),                 new PropertyMetadata(false, OnIsExportingValueChanged));          private static void OnIsExportingValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             FrameworkElement element = d as FrameworkElement;              if (element == null)                 return;              if ((e.NewValue as bool?) == false)                 return;              try             {                 string exportPath = GetExportPath(d);                 if (string.IsNullOrEmpty(exportPath))                 {                     exportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),                         "Export");                 }                  if (!Directory.Exists(exportPath))                 {                     Directory.CreateDirectory(exportPath);                 }                  string filePath = Path.Combine(exportPath, $"{DateTime.Now:yyyyMMddHHmmss}.png");                  bool success = ExportPicHelper.SaveToImage(element, filePath, out string errorMsg);                 if (success)                 {                     MessageBox.Show($"导出成功");                 }                 else                 {                     Console.WriteLine($"导出失败:{errorMsg}");                     MessageBox.Show($"导出失败 {errorMsg}");                 }             }             catch (Exception ex)             {                 Console.WriteLine($"导出异常:{ex}");                 MessageBox.Show($"导出异常:{ex.Message}");             }             finally             {                 //此处设置为 false 没什么用,还是需要业务层在设置为 true 前先设置为 false 才行。                 SetIsExporting(d, false);             }         }          #endregion          #region 导出文件夹          public static string GetExportPath(DependencyObject obj)         {             return (string)obj.GetValue(ExportPathProperty);         }          public static void SetExportPath(DependencyObject obj, string value)         {             obj.SetValue(ExportPathProperty, value);         }          /// <summary>         /// 导出文件夹路径         /// </summary>         public static readonly DependencyProperty ExportPathProperty =             DependencyProperty.RegisterAttached("ExportPath", typeof(string), typeof(ExportPicAttached), new PropertyMetadata(string.Empty));          #endregion     } }

 

ExportPicAttached 类中包含两个附加属性,一个是导出文件夹路径 ExportPath,一个是是否开始导出 IsExporting。当 IsExporting 被设置为 true 则开始导出,如果导出文件夹路径没被设定,则导出到桌面文件夹,然后就是调用方案二中出现的 ExportPicHelper.SaveToImage 方法。

 

使用方法就是在要导出的元素上设置上这两个附加属性,然后把值进行绑定:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

在 ViewModel 中,先设定导出路径,然后把 IsExporting 置为 true 即可开始导出:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

也是能正常导出的:

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

这个方案结合了前两个方案的优点,既能导出所有想要的内容,又对 MVVM 友好。

缺点就是导出的控制有点奇怪,需要先将 IsExporting 置为 false,不然第二次就导出不了了。尝试了在附加属性逻辑中自动置为 false,但是好像值传递不到 VM 中的相关绑定属性中,有了解解决方法的朋友们请不吝赐教。

OxyPlot 导出图片及 WPF 元素导出为图片的方法

 

源码地址:https://gitee.com/dlgcy/DLGCY_OxyPlotTester/tree/Blog20220226

 

全文完,感谢阅读,祝大家天天开心。