一.基础知识
1.System.Printing命名空间
我们可以先看一下System.Printing命名空间,东西其实很多,功能也非常强大,可以说能够控制打印的每一个细节,曾经对PrintDialog失望的我看到了一丝曙光。
2.PrintDialog
可以看到PrintDialog除了构造函数有三个方法和一堆属性,PrintDocument接受一个分页器(DocumentPaginator,稍后介绍),PrintVisual可以打印Visual,也就是WPF中的大部分继承自Visual类的UI对象都可以打印出来,最后一个是ShowDialog方法,其实就是显示一个界面,可以配置一下纸张选择,横向打印还是纵向打印,但是其打印范围页的功能是没有实现的,无论怎么配置,都是全部打印出来,这个稍后会有解决办法。
至此,可以看出如果我们要随心所欲打印自己的东西那么PrintDialog一个是不够用的,要能够打印自定义的内容我们需要使用到强大的DocumentPaginator。
3.DocumentPaginator
DocumentPaginator是一个抽象类,我们继承其看需要重写哪些东西
class TestDocumentPaginator : DocumentPaginator
{
public override DocumentPage GetPage(int pageNumber)
{
throw new NotImplementedException();
} public override bool IsPageCountValid
{
get
{
return true;
}
} public override int PageCount
{
get
{
throw new NotImplementedException();
}
} public override Size PageSize
{
get;
set;
} public override IDocumentPaginatorSource Source
{
get
{
return null;
}
}
}
注意GetPage方法,这个很重要,这也是分页器的核心所在,我们根据传入的页码返回内容DocumentPage,IsPageCountValid直接设置为True即可,PageCount即总页数,这个需要我们根据需求来分页来计算,PageSize就是纸张的大小,至于Source是用在什么地方还真没研究过,直接返回null。如何实现自定义打印稍后介绍。
3.PrintServer && PrintQueue
PrintServer可以获取本地的打印机列表或网络打印机,PrintQueue实际上代表的就是一个打印机,所以我们就能够获取到本地计算机上已经配置的打印机,还能够获取默认打印机哦
private void LoadPrinterList()
{
var printServer = new PrintServer(); //获取全部打印机
PrinterList = printServer.GetPrintQueues(); //获取默认打印机
DefaultPrintQueue = LocalPrintServer.GetDefaultPrintQueue();
}
4.PageMeidaSize && PageMediaSizeName
PageMediaSize包含了纸张的宽和高以及名称,PageMediaSizeName是一个枚举,把所有纸张的名称都列举出来了,所以我们就能够获取到打印机支持的纸张类型集合了
var pageSizeCollection = DefaultPrintQueue.GetPrintCapabilities().PageMediaSizeCapability;
二.自定义打印原理
我们看一下DocumentPage这个对象,构造函数需要传入一个Visual对象,打印的每一页其实就是打印每一页的Visual,这就好办了,WPF中有一个Visual的派生类DrawingVisual,DrawingVisual好比一个“画板”,我们可以在上面任意作画,有了画板我们还要拥有“画笔”DrawingContext。马上演示如何在画板上作画
private void DrawSomething()
{
var visual = new DrawingVisual(); using (DrawingContext dc = visual.RenderOpen())
{
dc.DrawRectangle(Brushes.Black, new Pen(Brushes.Black, ), new Rect(, , , ));
}
}
这样我就在左上角绘制了一个宽100高100的矩形,DrawingContext的方法很多
可以看到能够绘制许多基本的东西,如图片,文本,线段等。
到这儿,大家都该清楚了,自定义打印的原理就是使用DrawingVisual绘制自己的内容,然后交给DocumentPage,让打印机来处理。
下面演示一下打印5个页面,每个页面左上角显示页码
TestDocumentPaginator.cs
class TestDocumentPaginator : DocumentPaginator
{ #region 字段
private int _pageCount;
private Size _pageSize;
#endregion #region 构造
public TestDocumentPaginator()
{
//这个数据可以根据你要打印的内容来计算
_pageCount = ; //我们使用A3纸张大小
var pageMediaSize = LocalPrintServer.GetDefaultPrintQueue()
.GetPrintCapabilities()
.PageMediaSizeCapability
.FirstOrDefault(x => x.PageMediaSizeName == PageMediaSizeName.ISOA3); if (pageMediaSize != null)
{
_pageSize = new Size((double)pageMediaSize.Width, (double)pageMediaSize.Height);
}
}
#endregion #region 重写
/// <summary>
///
/// </summary>
/// <param name="pageNumber">打印页是从0开始的</param>
/// <returns></returns>
public override DocumentPage GetPage(int pageNumber)
{
var visual = new DrawingVisual(); using (DrawingContext dc = visual.RenderOpen())
{
//设置要绘制的文本,文本字体,大小,颜色等
FormattedText text = new FormattedText(string.Format("第{0}页", pageNumber + ),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("宋体"),
,
Brushes.Black); //文本的左上角位置
Point leftpoint = new Point(, ); dc.DrawText(text, leftpoint);
} return new DocumentPage(visual, _pageSize, new Rect(_pageSize), new Rect(_pageSize));
} public override bool IsPageCountValid
{
get
{
return true;
}
} public override int PageCount
{
get
{
return _pageCount;
}
} public override Size PageSize
{
get
{
return _pageSize;
}
set
{
_pageSize = value;
}
} public override IDocumentPaginatorSource Source
{
get
{
return null;
}
}
#endregion
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
PrintDialog p = new PrintDialog(); TestDocumentPaginator docPaginator = new TestDocumentPaginator(); p.PrintDocument(docPaginator, "测试");
}
注意,这里我使用了MicroSoft的虚拟打印机XPS,然后使用XPS查看器查看
这样一共5页
三.打印范围页
我在使用PrintDialog的时候,尝试过打印范围页,就通过设置PrintDialog的几个参数,但都失败了,网上一搜,遇到此问题的少年还不少,于是网上有许多办法,比较容易搜到的是一个从PrintDialog派生类然后自己处理打印范围页,这个方法想法是好的,但是内部理解起来不容易,一定有更合适的方法,于是各种搜索(Google不能用了,只好用Bing),搜到这么一篇文章How to print a PageRange with WPF’s PrintDialog,文章没有讲得很清晰,其实原理很简单
对,分页器的分页器…,我们使用第二个分页器,在页码上加上一个基数,取第一个分页器的页面,也不知大家看明白没有,算了,我还是上代码吧
PageRangeDocumentPaginator.cs
class PageRangeDocumentPaginator : DocumentPaginator
{
private int _startIndex;
private int _endIndex;
private DocumentPaginator _paginator; public PageRangeDocumentPaginator(int startIndex, int endIndex, DocumentPaginator paginator)
{
_startIndex = startIndex;
_endIndex = endIndex;
_paginator = paginator;
} public override DocumentPage GetPage(int pageNumber)
{
return _paginator.GetPage(pageNumber + _startIndex);
} public override bool IsPageCountValid
{
get
{
return _paginator.IsPageCountValid;
}
} public override int PageCount
{
get
{
return _endIndex - _startIndex + ;
}
} public override Size PageSize
{
get
{
return _paginator.PageSize;
}
set
{
_paginator.PageSize = value;
}
} public override IDocumentPaginatorSource Source
{
get
{
return null;
}
}
}
这个方法实现很简单,也很巧妙。
四.打印预览
我们有了分页器,并且能够从分页器中GetPage(int pageNumber),得到某一页的DocumentPage,DocumentPage中包含了我们绘制的Visual,这个时候就可以将Visual拿出来,用一个Canvas在窗口上显示出来,达到一个预览的效果,但Canvas需要特殊处理一下
DrawingCanvas.cs
class DrawingCanvas : Canvas
{
#region 字段
private List<Visual> _visuals = new List<Visual>();
#endregion #region 公有方法 public void AddVisual(Visual visual)
{
_visuals.Add(visual); base.AddLogicalChild(visual);
base.AddVisualChild(visual);
} public void RemoveVisual(Visual visual)
{
_visuals.Remove(visual); base.RemoveLogicalChild(visual);
base.RemoveVisualChild(visual);
} public void RemoveAll()
{
while (_visuals.Count != )
{
base.RemoveLogicalChild(_visuals[]);
base.RemoveVisualChild(_visuals[]); _visuals.RemoveAt();
}
} #endregion #region 构造 public DrawingCanvas()
{
Width = ;
Height = ;
}
#endregion #region 重写
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
} protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
#endregion
}
这样就可以直接用Canvas直接Add我们的Visual了
五.异步打印
为什么会想到使用异步打印呢?当要打印的页面数量非常大的时候,比如400多页,在使用PrintDialog.PrintDocument的时候,会卡住界面很久,这不是我们所希望的。
其实PrintDialog内部是使用了XpsDocumentWriter的,它有一个WriteAsync方法
var doc = PrintQueue.CreateXpsDocumentWriter(queue); doc.WriteAsync(new PageRangeDocumentPaginator(startIndex, endIndex, p));
但是啊,这么做还是不能完全解决界面卡住的问题,为什么呢?因为我们的分页器使用了DrawingVisual,Visual是DispatcherObject的派生类,那么对它的使用是要占用UI线程资源的。而我们的分页器是在主UI线程中创建的,异步方法其实是另开一个线程去处理,那么这个线程对Visual的访问还是会切换到主线程上,要不就会报错…,好吧,干脆开一个线程,重新创建分页器,创建XpsDocumentWriter,整个一套都在一个单独的线程中执行,于是
Task.Factory.StartNew(() =>
{
try
{
var p = PaginatorFactory.GetDocumentPaginator(_config); p.PageSize = new Size(_paginator.PageSize.Width, _paginator.PageSize.Height); var server = new LocalPrintServer(); var queue = server.GetPrintQueue(queueName); queue.UserPrintTicket.PageMediaSize = PageSize; queue.UserPrintTicket.PageOrientation = PageOrientation; var doc = PrintQueue.CreateXpsDocumentWriter(queue); }
catch (Exception ex)
{ }
finally
{
_dispatcher.BeginInvoke(new Action(() => Close()));
}
});
一试,界面完全不卡,因为这个时候已经不关UI线程的事了,需要注意一点就是,已经单独在一个线程中,那么就不需要使用异步打印方法了即WriteAsync,使用Writer即可,大家试一下就知道了。
六.源码
项目是一个实现打印预览的功能,目前我已经实现了DataTable的打印预览,BitmapImage和FrameworkElement的打印预览,后两者暂不支持完全异步的打印。
源码托管在开源中国:https://git.oschina.net/HelloMyWorld/HappyPrint.git,第一次把自己的东西共享出来,希望大家支持和斧正。
参考资料:《WPF编程宝典》第29章打印
欢迎转载,转载请注明出处
WPF打印原理,自定义打印的更多相关文章
-
Objective-C与Swift下的自定义打印函数(Debug和Release)
1.Objective-C 在使用Objective-C进行开发的过程中,为了Debug会不断的设置打印函数.如下图是我们经常用的,用来测试监听方法的实现与否: NSLog(@"%s&quo ...
-
Swift 自定义打印方法
Swift 自定义打印方法 代码如下 // MARK:- 自定义打印方法 func MLLog<T>(_ message : T, file : String = #file, funcN ...
-
Flex 自定义打印控件编写
打印历来是web应用一个比较棘手的问题,幸好flex web应用是运行在flash player上的,flash player可以访问打印机,所以flex 应用可以实现比较强大的打印功能.Flex 自 ...
-
U9单据打印模板自定义扩展字段显示名称
UBF打印模板中,单据自定义扩展字段显示均为扩展字段值集值编码,而在实际运用过程中打印时需要显示扩展字段名称,具体实现方法如下 方式一:采用SQL系统定义函数[dbo].[fn_GetSegName] ...
-
如何使用ArcGIS Pro发布自定义打印服务
我们知道可以通过ArcGIS Map来发布自定义打印服务.从ArcGIS Enterprise 10.6.1版本起,打印服务的功能更加完善了,改进点包括: 支持打印矢量切片服务 改进了智能制图和颜色透 ...
-
Python 日志打印之自定义logger handler
日志打印之自定义logger handler By:授客 QQ:1033553122 #实践环境 WIN 10 Python 3.6.5 #实践代码 handler.py #!/usr/bin/env ...
-
OC语言自定义打印
1.为了全文通用,选择在PCH文件中写: // // 版权所有:Copyright © 2018年 Lelight. All rights reserved. // 创 建 者: Lelight // ...
-
WPF中实现自定义虚拟容器(实现VirtualizingPanel)
WPF中实现自定义虚拟容器(实现VirtualizingPanel) 在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容 ...
-
WPF 渲染原理
原文:WPF 渲染原理 在 WPF 最主要的就是渲染,因为 WPF 是一个界面框架.想用一篇博客就能告诉大家完整的 WPF 渲染原理是不可能的.本文告诉大家 WPF 从开发者告诉如何画图像到在屏幕显示 ...
随机推荐
-
oracle基本操作
登入oraclesqlplus / as sysdba启动oraclestartup停止oracleshutdown 创建新用户create user username identified by p ...
-
MongoDB的学习--索引
索引可以用来优化查询,而且在某些特定类型的查询中,索引是必不可少的.为集合选择合适的索引是提高性能的关键. 先来mock数据 for (i = 0; i < 1000000; i++) { db ...
-
asp.net多图片上传实现程序代码
下面是一个完整的asp.net同时支持多图片上传一个实现,有需要的朋友可参考一下,本文章限制同时可上传8张图片,当然大可自己可修改更多或更少. 前台代码如下: 复制代码代码如下: <% @ Pa ...
-
(转)JQuery中$.ajax()方法参数详解
url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如put和 ...
-
Property type &#39;id<;tabBarDelegate>;&#39; is incompatible with type &#39;id<;UITabBarDelegate>; _Nullable&#39; inherited from &#39;UITabBar&#39;
iOS报错:Property type 'id' is incompatible with type 'id _Nullable' inherited from 'UITabBar' 如图: 可能原因 ...
-
Android 窗口全屏
全屏getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 取消全屏 getWindow().clearFlags(Wind ...
-
python之线程学习
一.进程与线程简介 进程 进程是程序的一次执行,由进程段.数据段.进程控制块三部分组成.具体三个基本状态,就绪.执行.阻塞,是一个拥有资源的独立单位. 线程 属于进程的一个实体,拥有极少的资源.也具有 ...
-
详解.NET IL代码(一)
本文主要介绍IL代码,内容大部分来自网上,进行整理合并的. 一.IL简介 为什么要了解IL代码? 如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以 ...
-
C++STL3--queue
C++STL3--queue 一.心得 STL的这些东西用法都差不多 二.介绍 queue数据结构中的队列 priority_queue优先队列,插入进去的元素都会从大到小排好序 PS:在priori ...
-
【Javascript Demo】无刷新预览所选择的图片
1.效果如下,可测试 2.代码如下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " ...