大家都知道,C#打印图片可以直接调用PrintDocument控件的PrintPage事件,通过画刷对image对象直接进行绘制。但是这种方法存在局限,例如如果打印的图片需要按纸张大小进行缩放的话,那么图片显示比例和图片显示位置等都需要动态计算,如果还要添加水印或者其他的图片操作,基本上要添加很多额外的逻辑,并且效率不高,严重限制了程序的性能。如果要在图片上绘制个性化的文本或者定制其他内容,则基本没办法实现,严重限制了程序的可扩展性和可维护性。

常规写法如下所示:

 //例如这是PrintDocument的PrintPage事件
Graphics g = null;
g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality; //设置画刷高质量 //绘制已经处理过的Bitmap对象(假设它已经从服务器或者某个地方下载下来并且已经算好了在纸张上指定的打印位置)
//此种写法在激光高速打印机中存在明显缺陷,因此不建议大业务量的程序使用
g.DrawImage(bmp, locationX, locationY, bmp.Width, bmp.Height);

笔者使用某款佳能的普通喷墨打印机速度不是很理想,在实际业务需求量很大的情况下,采用了利盟牌某款高速激光打印机,但是调用PrintDocument控件对图像对象进行绘制时,打印机处于等待状态,虽然打印任务已经发送过去,但是由于图片尚未绘制完成,所以打印机停滞不出纸张,效率还不如普通喷墨打印机,可以说这是这种方法的局限性导致的,因此在大业务量的情况下,使用这种方法进行打印明显并不合适。

所以我就开始研究使用模板进行打印的方法。

本文所探讨的是使用FastReport第三方控件对图片打印进行个性化的模板定制。首先在项目中引入此控件的相关dll。

第一步:添加FastReport模板打印相关类和方法:

在打印类中,我们定义一个打印模板的方法,关键代码如下:

         /// <summary>
/// 打印报表
/// </summary>
/// <param name="ht">fastreport参数(key)及值(value)</param>
/// <param name="ds">数据集</param>
/// <param name="functionCode">模板名称</param>
/// <param name="modelCode">模板类型</param>
/// <param name="selectPrint">是否选择打印机bool</param>
public void Print(NoSortHashTable ht, DataSet ds, string functionCode, string modelCode, bool selectPrint)
{
//假设模板数据存在数据库中,此时先获取模板数据
Model.FR_Template m = this.GetFastReportModel(functionCode, modelCode, orgid); if (m == null)
{
throw new Exception("调用的模版不能为空!");
}
//设置模版数据
this.TempContent = string.IsNullOrEmpty(this.TempContent) ? m.TEMPCONTENT : this.TempContent; //TempInf为空时报错
if (!string.IsNullOrEmpty(this.TempContent))
{
//导入模版数据
this.report.LoadFromString(this.TempContent);
} if (FRds != null)
{
// 注册报表数据
this.report.RegisterData(ds, FRds.DataSetName); //加载可用的数据源
foreach (DataTable dt in FRds.Tables)
{
this.report.GetDataSource(dt.TableName).Enabled = true;
}
}
//动态添加fastreport参数
foreach (DictionaryEntry de in ht)
{
string ParamName = de.Key.ToString();
//获取参数
FastReport.Data.Parameter param = this.report.Parameters.FindByName(ParamName);
if (param != null)
{
param.Value = de.Value;
}
} this.report.PrintSettings.ShowDialog = selectPrint; string printerName = ConfigurationManager.AppSettings[functionCode] == null ? "" : ConfigurationManager.AppSettings[functionCode].ToString();
if (!string.IsNullOrEmpty(printerName))
{
this.report.PrintSettings.Printer = printerName;
}
// 运行报表打印
this.report.Print();
// 释放使用的资源
this.report.Dispose();
}

在设置打印模板的界面中,打开FastReport设计器的代码如下:

 SaveFRTemplateFrm saveFRTfrm = new SaveFRTemplateFrm(VoidNameEnum.Update, dgvr);
saveFRTfrm.Owner = this;
saveFRTfrm.StartPosition = FormStartPosition.CenterScreen;
if (saveFRTfrm.ShowDialog() == DialogResult.OK)
{
//保存设计好的打印模板
}

第二步:传递参数和数据,调用打印 

模板设计好之后,在打印的界面中需调用刚刚封装的打印方法对模板进行传参打印。一下为打印方法:

      /// <summary>
/// 从打印模板打印数据
/// </summary>
private void PrintPaperByTemplet( DataTable dsRSPrint)
{
NoSortHashTable nht = new NoSortHashTable();
SavePrintTempFile(); //添加打印参数
nht.Add("打印页码", (pagenum == ? "" : Currentpagenum + "/" + pagenum));
nht.Add("打印时间", AppData.SysDate.ToString("yyyy-MM-dd hh:mm:ss")); DataSet ds = new DataSet("DataPrint");
ds.Tables.Add(dsRSPrint.Copy()); try
{
Print(nht, ds, functionCode, "A4", false);
Application.DoEvents();
}
catch (Exception ex)
{
//errorMsg += "图片数据出现问题,无法输出到打印模板!\n";
}
}

那么关键的地方是,打印的图片数据如传入到FastReport模板中呢?有如下两种方法供你参考:

方法一:

在FastReport模板中添加图片对象的控件,指定本地或网络路径(注意必须是固定链接)的图片名称,每次打印之前先把需要打印的图片存放到这个路径并命名成指定的文件名。

        /// <summary>
/// 保存打印模板用到的临时缓存文件
/// </summary>
private void SavePrintTempFile()
{
bool isSaveFlag = true;
do
{
try
{
if (File.Exists("某个文件.jpg"))
{
File.Delete("某个文件.jpg");
Thread.Sleep(); //休眠 避免保存文件时图片尚未删除
}
img.Save(printTempFile); using (Bitmap bmpPrint = new Bitmap(img))
{
//对图片进行一些处理,例如压缩大小,调整对比度等等
}
}
catch (Exception ex)
{
isSaveFlag = false;
}
} while (!isSaveFlag); //将文件设置为隐藏
FileInfo fi = new FileInfo(printTempFile);
File.SetAttributes(printTempFile, fi.Attributes | FileAttributes.Hidden);
}

当然如果你想在图片上添加水印,在模板中也可以实现,例如下图片所示,在图片层上面指定水印图片,注意必须是PNG格式的矢量图形,否则会盖住原始的图片内容。

水印的添加设置方法同上面的图片添加,在模板中设置指定路径即可。

方法二:

将图片对象通过数据列或参数形式传递到模板中,注意需要将image对象格式转化为64位字符串。

 Byte[] streamByte = ImageBytesHelper.GetByteImage(img);  //先将image对象转化为二进制字节(过程略)
dataRow["图片数据"] = Convert.ToBase64String(streamByte); //再将字节转换为64为字符

在模板中,你需要添加部分事件代码解析传过来的图片数据。

//在模板的DataPrintBefore事件中写下如下代码
string imgStr = (string)Report.GetColumnValue("ds.图片数据"); byte[] imgData=Convert.FromBase64String(imgStr);
MemoryStream ms = new MemoryStream(imgData);
Image img = System.Drawing.Image.FromStream(ms); //PictureObject pic=Report.FindObject('Picture1') as PictureObject;
Picture1.Image=img;

此时image对象的64位字符即可以解析为图片显示在模板上了。

这两种方法是我研究了一段时间的结果,第一种直接存文件每次读取简单有效,并不影响打印效率。第二种方法传参设置,比第一种方法稍微复杂,但不需要读盘,稳定型更好。

注意:本文为Healer007原创,署名为小萝卜,本人站点:itoku.cn,欢迎交流学习,转载文章请注明出处。

C#:使用FastReport打印带图片传参模板的实现方法的更多相关文章

  1. java:打印菱形图案(传参打印的自定义字符和行数)

    打印菱形图案: 代码实现: public class Hello { public static void main(String args[]) { LingXingPrint("#&qu ...

  2. Angular页面传参的四种方法

    1. 基于ui-router的页面跳转传参 (1)在Angular的app.js中用ui-route定义路由,比如有两个页面, 一个页面(producers.html)放置了多个producers,点 ...

  3. flask开启debug模式的两种方法、加载配置文件的两种方法、URL传参的四种方法

    from flask import Flask app = Flask(__name__) # app.config.update(DEBUG=True)#开启debug模式 #加载配置文件方法一 # ...

  4. 微信小程序的页面跳转==编程式导航传参 和 标签的方法传参==以及如何过去传递过来的参数

    小程序导航传参接收传递过来的参数 在onload中 实例

  5. angularJS 传参的四种方法 【修改】

    1. 基于ui-router的页面跳转传参(1) 在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个produce ...

  6. angularJS 传参的四种方法

    AngularJS - Passing data between pages 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:Ye Huang链接:https://www.z ...

  7. vue 路由传参的三种方法

    API在这里  https://router.vuejs.org/guide/essentials/navigation.html 第一种传参 通过路由属性中的name来确定匹配的路由,通过param ...

  8. Angular5 路由传参的3种方法

    一共3种方法. 1.问号后面带的参数,获取参数的方式:ActivatedRoute.queryParams[id] 例如:/product?id=1&name=iphone还可以是: [rou ...

  9. vue路由跳转传参的两种方法

    路由跳转: this.$router.push({ name: '工单列表', params: {p_camera_dev_name: 'xxx'} }); 使二级菜单呈点击状态: $('[index ...

随机推荐

  1. 【Flutter学习】基本组件之AppBar顶部导航栏

    一,概述 AppBar 显示在app的顶部.AppBar包含5大部分,如下图: 二,构造函数及参数含义 构造函数 AppBar({ Key key, this.leading, //在标题前面显示的一 ...

  2. react 子组件给父组件传值

    import React from 'react'import '../page1/header.css'import { Table } from 'antd'import Child from ' ...

  3. 经典的GDB调试命令,包括查看变量,查看内存

    经典的GDB调试命令,包括查看变量,查看内存 在你调试程序时,当程序被停住时,你可以使用print命令(简写命令为p),或是同义命令inspect来查看当前程序的运行数据.print命令的格式是: p ...

  4. 新版本的molar mass(uva-1586)明明debug过了,各种测试还是WA真是气死我了

    #include <bits/stdc++.h> using namespace std; double trans(string a) { stringstream ss; ss< ...

  5. Cent os常见操作命令

    1.查看防火墙状态:firewall-cmd –-state 2.关闭防火墙:systemctl stop firewalld.service 3.禁止防火墙开机启动:systemctl disabl ...

  6. PAT 1094. The Largest Generation (层级遍历)

    A family hierarchy is usually presented by a pedigree tree where all the nodes on the same level bel ...

  7. Huawei-R&S-网络工程师实验笔记20190609-VLAN划分综合(Access和Trunk端口)

    >Huawei-R&S-网络工程师实验笔记20190609-VLAN划分综合(Access和Trunk端口) >>实验开始,先上拓扑图参考: >>>实验目标 ...

  8. 【codeforces 760D】Travel Card

    [题目链接]:http://codeforces.com/contest/760/problem/D [题意] 去旅行,有3种类型的乘车票; 第一种:只能旅行一次20元 第二种:按时间计算,90分钟内 ...

  9. windows 2013(codevs 1695)

    题目描述 Description 话说adamyi编的Windows 2013超时了(- -!),所以他不得不在自己家门口亲眼见证这个电影般的场景.虽然他不想错过这个美妙的时刻,但是他的肚子一再抗议, ...

  10. ZooKeeper教程资源收集(简介/原理/示例/解决方案)

    菩提树下的杨过: ZooKeeper 笔记(1) 安装部署及hello world ZooKeeper 笔记(2) 监听数据变化 ZooKeeper 笔记(3) 实战应用之[统一配置管理] ZooKe ...