一.基础知识

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打印原理,自定义打印的更多相关文章

  1. Objective-C与Swift下的自定义打印函数(Debug和Release)

    1.Objective-C 在使用Objective-C进行开发的过程中,为了Debug会不断的设置打印函数.如下图是我们经常用的,用来测试监听方法的实现与否: NSLog(@"%s&quo ...

  2. Swift 自定义打印方法

    Swift 自定义打印方法 代码如下 // MARK:- 自定义打印方法 func MLLog<T>(_ message : T, file : String = #file, funcN ...

  3. Flex 自定义打印控件编写

    打印历来是web应用一个比较棘手的问题,幸好flex web应用是运行在flash player上的,flash player可以访问打印机,所以flex 应用可以实现比较强大的打印功能.Flex 自 ...

  4. U9单据打印模板自定义扩展字段显示名称

    UBF打印模板中,单据自定义扩展字段显示均为扩展字段值集值编码,而在实际运用过程中打印时需要显示扩展字段名称,具体实现方法如下 方式一:采用SQL系统定义函数[dbo].[fn_GetSegName] ...

  5. 如何使用ArcGIS Pro发布自定义打印服务

    我们知道可以通过ArcGIS Map来发布自定义打印服务.从ArcGIS Enterprise 10.6.1版本起,打印服务的功能更加完善了,改进点包括: 支持打印矢量切片服务 改进了智能制图和颜色透 ...

  6. Python 日志打印之自定义logger handler

    日志打印之自定义logger handler By:授客 QQ:1033553122 #实践环境 WIN 10 Python 3.6.5 #实践代码 handler.py #!/usr/bin/env ...

  7. OC语言自定义打印

    1.为了全文通用,选择在PCH文件中写: // // 版权所有:Copyright © 2018年 Lelight. All rights reserved. // 创 建 者: Lelight // ...

  8. WPF中实现自定义虚拟容器(实现VirtualizingPanel)

    WPF中实现自定义虚拟容器(实现VirtualizingPanel) 在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容 ...

  9. WPF 渲染原理

    原文:WPF 渲染原理 在 WPF 最主要的就是渲染,因为 WPF 是一个界面框架.想用一篇博客就能告诉大家完整的 WPF 渲染原理是不可能的.本文告诉大家 WPF 从开发者告诉如何画图像到在屏幕显示 ...

随机推荐

  1. Java Concurrency In Practice -Chapter 2 Thread Safety

    Writing thread-safe code is managing access to state and in particular to shared, mutable state. Obj ...

  2. Hadoop从伪分布式到真正的分布式

    对这两天学习hadoop的一个总结,概念就不提了.直接说部署的事,关于如何部署hadoop网上的资料很多, 比较经典的还是Tim在IBM developworks上的系列文章 http://www.i ...

  3. MySQL数据库初识(一)

    MySQL是一种免费的小型关系型数据库,与Linux.Apache/Nginx.PHP一起组成了WEB开发的黄金搭档. MySQL是C/S(客户端/服务端)体系结构的软件,而在开发中,PHP承担起了客 ...

  4. C# PDF打印

    C#中使用iTextSharp生成并下载PDF很方便. 首先要将iTextSharp的dll下载并引入项目 主要分成两部分,一部分是PDF的Document生成,另一部分就是将Document输出到页 ...

  5. hdu 4738 Caocao's Bridges 图--桥的判断模板

    Caocao's Bridges Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  6. Spring @Transactional ——事务回滚

    工作原理运行配置@Transactional注解的测试类的时候,具体会发生如下步骤1)事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与Dat ...

  7. js控制iframe跳转

    网页:<iframe src="" id="iframe_a" name="iframe_a" style="width:1 ...

  8. A python tool to static sim.log duration time

    When working ALU IMS Patch team, we need to static the SU duration to add it to the patch report, th ...

  9. linux内核宏container_of前期准备之gcc扩展关键字typeof

    typeof基本介绍 typeof(x) 这是它的使用方法,x可以是数据类型或者表达式.它的作用时期和sizeof类似,就是它是在编译器从高级语言(如C语言)翻译成汇编语言时起作用,这个很重要,稍后会 ...

  10. Snowflake weakness and type2 fact table

    DimProduct DimSubcategory Dimcategory productpk subcategorypk categorypk sku subcategoryName categor ...