热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

掌握.NET中的日常打印

掌握.NET中的日常打印下载本文代码下载本期杂志代码见资源运用.NETFramework中的类来创建看上去很专业的报表。byMichaelEaton技术工具箱:C#你可以用几种方法在.NE
掌握.NET中的日常打印


下载本文代码
下载本期杂志代码
见资源
运用.NET Framework中的类来创建看上去很专业的报表。
by Michael Eaton
技术工具箱:C#

你可以用几种方法在.NET中编程来生成打印输出结果(如报表)。对Windows程序员来说,Visual Studio提供的Crystal Reports实际上是人们常用的打印报表的工具,但对不太复杂的报表来说,这个工具就有些大材小用了。当然,你可以用很好的Win32 API调用,但是尽管API可以让你有完全的控制权,它同时也把你锁定到一个单一的平台上了。如果Microsoft或另外的公司(如拥有Mono项目的Ximian公司)把Framework转移到另外的平台上,那么运用API的程序仍会锁定在Windows上,除非你为新的平台重新编写它们。

.NET Framework可以让你以一种新的方式来使用这些打印方法,System.Drawing.Printing名字空间的类将Win32 API的细粒度控制(fine-grain control)与相对简单的Visual Basic传统的Printer对象结合了起来(见表1)。在此,我将重点讲述该名字空间的主要的类,它们可以让你创建复杂程度适度的报表。PrintDocument类是这些类中最重要的。它可以让你定义一个对象,该对象发送输出结果到一个目的地,可以是一台打印机,或者是显示一个打印预览(运用PrintPreviewDialog类)。我还会讲述PrinterSettings类,它可以让你控制文件是如何打印的。

你可以很容易地在C#中创建一个PrintDocument对象,并把它附到一个事件处理程序上(实际处理打印的代码):

PrintDocument doc = new    PrintDocument();   doc.PrintPage += new       PrintPageEventHandler(      doc_PrintPage);

PrintPage事件处理程序有一个PrintPageEventArgs类型的参数。这个参数包含与该事件相关的数据,包括当前页面的页面设置、页边距信息以及是否有更多的页需要打印。每个页面触发该事件一次,直到ev.HasMorePages等于false:

private void doc_PrintPage(object    sender, PrintPageEventArgs ev)

将Graphics用于Fonts和Fills
ev参数引用了你用来输出数据的Graphics制图区。Graphics有用来打印图象、文本、形状和线条的方法。你可以用DrawString方法来定义你要用的font(字体)和brush(画刷)。一个brush定义了一个图形的内部是如何填充的。你也需要告诉DrawString方法你想在哪里打印文本。PrintPageEventArgs类有一个读写属性——HasMorePages——它指明是否需要打印更多的页面:

Font f = new Font("Arial", 12);ev.Graphics.Drawstring("Hello,    World.",   f, Brushes.Black, 100, 100);ev.HasMorePages = false;

现在,你已经创建了一个PrintDocument对象并把它和一个事件处理程序联系起来了。你已经给事件处理程序中添加了代码,来打印一个单独的字符串,它的位置是距页面左边一英寸、距页面顶部一英寸,用的是12磅、 Arial的字体。现在我们来打印文件:

doc.Print();

在缺省设置下,PrintDocument类的Print方法在缺省打印机上打印结果,除非你另外指定。你可以用PrintDialog类来另外指定,它是System.Windows.Forms.CommonDialog名字空间的一部分。PrintDialog可以让你选择打印机,选择打印文件的哪部分,选择打印份数,来随意地打印一个文件。

PrintDialog类的PrinterSettings属性可以让你将诸如copies、from pageto page的属性放置到一个特定文件的PrinterSettings对象中。换句话说,单独的PrintDocument对象有一个PrinterSettings对象,它指定打印的份数、打印的范围、要运用的打印机的名字以及关于打印机本身的信息。

IPrinterSettings类中的一个bug会导致Copies属性总是返回一个为1的值,而不管你在PrintDialog中输入了多少份数。然而,PrintDocument.Print()方法仍然打印正确的份数,所以只有当你想将这个值用于其它地方时,才显示这个bug。当你执行Print方法时,系统运用这些设置。你可以提供一个实际的PrinterSettings对象或提供一个Document对象:

PrintDialog pd = new PrintDialog();pd.Document = doc;pd.ShowDialog();

然后,运用CommonDialog名字空间的PageSetupDialog,你可以指定纸张大小、纸张来源、打印方向和页边距。PageSetupDialog和PrintDialog都需要你提供一个Document对象或一个PageSettings对象。PageSettings对象适用于一个单独的打印页面,可以处理页边距、纸张大小以及是否用颜色打印页面:

PageSetupDialog ps = new    PageSetupDialog();ps.Document = doc;ps.ShowDialog();

打印一个文件
当然,许多应用程序打印存储在一个文件或某种数据库中的信息,所以现在我将讲述一个更复杂的例子:打印一个以逗号分隔的联系清单文本文件。你可以将一个数据集、甚至一个XML文件中数据所运用的方法用在这里。本例的数据来自Northwind数据库的Customers表,格式如下:

Name, Title, Phone, Fax

通过在类的级别声明一个StreamReader对象,然后将一个文件分配给它,你就可以从一个文本文件打印该例子了。打开文件,修改PrintPage事件处理程序来打印文件的每一行,按需要格式化数据。

PrintDocument类有好几个事件。你已经用了PrintPage事件了;现在我们来添加更多的事件。当开始一个打印任务时(在实际打印任何东西前),触发BeginPrint事件。相应的事件是EndPrint,该事件是在所有页面结束打印时触发的。创建PrintPage事件需要的任何对象,或在BeginPrint中打开任何数据源,然后在EndPrint中关闭或释放(deallocate)它们。不要忘记将这些事件绑定到PrintDocument对象(见列表1)。

图1.
图1. 显示你的报表
作为选择,你也可以把所有的打印功能封装到PrintDocument派生的一个单独的类中,然后重载触发相应事件的方法。你可以用你自定义的类,而不用实例化一个PrintDocument对象(见 列表2)。这是一个很好的方法,但是为了保持一致,使代码清晰,我将继续讲述事件绑定方法。

PrintDocument类没有制图工具,所以创建报表需要花些时间——但这是值得的。创建一个新的报表的最好的方法是用一张空白的纸。在了解了报表的目的后,你就可以在纸上通过画方框来代表不同的区域进行设计了,如页眉、正文和页脚(见图1)。然后花些时间详细编写每个区域的数据将来自哪里,以及各个数据将放置在什么位置。可以说这个时候是你确保最终用户所看到的页面与你设计的页面是否是一致的最佳时刻。用户对报表比对应用程序的任何部分的挑剔都要多,所以你应该尽可能多花些时间来设计它。一旦你花了很多时间来设计报表并对它的打印结果了如指掌后,那么写代码就不会花很多时间了。

写代码
Rectangle对象提供了最快、最简单的方法来将你的页面设计转换成代码。这些对象可以让你几乎很准确地在屏幕上模拟你的设计。在页面上定义一个区域,指定坐标(有一定的高度和宽度)。Rectangle对象可以给你提供指定的点,在这个点上,你可以用Graphics类的Draw方法放置文本和图形。特别的是,你可以用DrawRectangle方法来查看该区域在页面的什么位置。该方法需要一个Pen对象(用来画直线和曲线)。将所有的版面设计代码放在PrintPage事件过程中:

// print a red line around the border // of the pageev.Graphics.DrawRectangle(   Pens.Red, leftMargin, topMargin,   pageWidth, pageHeight);

.NET Framework 1.0不能得到一台打印机的“hard”margins(实际可以打印到的页面最外面的区域)。但是,PrintPageEventArgs可以让你通过MarginBounds属性得到类似的功能。不幸的是,MarginBounds不考虑hard margins,所以你的输出可能不能在你期望的位置上结束。你必须用P/Invoke并调用Win32 GetDeviceCaps函数来得到打印机的hard margins(见列表3)。在得到hard margins后,你就可以开始创建Rectangle对象并在你的报表上打印信息了(见列表4)。

现在你已经创建了长方形边框(rectangles),你就可以在这个位置上打印你的数据了。一次打印一页数据,所以你需要定义一个页面有多大。用一个标准行的高度来划分可打印的区域(本例中你的正文区),从而计算每个页面的行数。通过用你运用的Font对象的GetHeight方法来确定一个行的高度。GetHeight是个属于Font类的重载的方法。你运用的方法需要一个Graphics对象参数。PrintPage事件的PrintPageEventArgs参数提供了这个Graphics对象:

int linesPerPage =    Convert.ToInt32(body.Height /    bodyFont.GetHeight(ev.Graphics));

一旦你确定了构成一个页面的行数,你就只需要进行简单的循环就行了,直到一个页面结束。然后设置ev.HasMorePages为true,或者一直等到打印的数据结束。在循环内部运用Graphics对象的Draw方法来打印你的数据。

你也需要确保DrawString方法将文本放置在了正确的位置上。通过用你选择的字体的高度乘以你用来跟踪已经打印了多少行的计数器,你就可以在每打印一行时计算下一行的位置了。然后为顶部的页边距添加值(见列表5)。

你运用的DrawString方法也接受一个StringFormat对象。StringFormat类可以让你控制文本的布局——包括对齐和行间距——以及省略符号的插入(如果一个给定的字符串对于你的长方形边框来说太长了时)。通过创建StringFormat对象,并设置其Alignment属性为StringAlignment.Center,你就可以使你的文本居中;然后将对象用于你调用的DrawString方法中。为你的联系清单页眉写以下代码:

StringFormat sf = new StringFormat();sf.Alignment = StringAlignment.Center;ev.Graphics.DrawString("Northwinds    Customer Contacts", headerFont,    defaultBrush, body, sf);

正如你所看到的,一旦你确定了报表的布局并创建了长方形边框来包含数据,实际的打印并不是很难。

图2.
图2. 预览打印
PrintPreviewControl和PrintPreviewDialog类在Printing名字空间中提供了很好的功能。PrintPreviewDialog封装了PrintPreviewControl类,并提供了一个很好的用户界面,用来在页面间导航、改变缩放比例、选择一次可以预览的页数(见 图2)。你可以用PrintPreviewControl来创建一个与你的应用程序其它部分相一致的打印预览窗口。一旦你定义了你的PrintDocument,并为必需的打印事件编写了代码后,添加打印预览就很简单了:
private void preview_Click(object    sender, System.EventArgs e) {   PrintPreviewDialog pd = new       PrintPreviewDialog();   pd.Document = doc;   pd.ShowDialog();}

我希望我已经给你们提供了研究System.Drawing.Printing名字空间的动力。我发现这个名字空间是用来自动生成许多重复性的打印项目(尤其是报表)的最好的方法。尝试用这种方法来完成一个打印任务吧,我敢打赌在用过一次后,你就会立刻将这个方法用于所有的打印任务了。

?

在控制台应用程序中实现打印


下载本文代码
你是否厌倦了将从控制台应用程序中选出的文本打印出来?这里将介绍一种方便的工具来帮你实现打印。
by Bill Wagner

我在以前的一篇文章中曾介绍到过如何从控制台命令中捕获输出结果并将它放到剪贴板中。我常常需要从一些控制台命令中将文本输出结果打印出来,因此我对最后的例子进行了修改以使它支持打印。

.NET打印框架对于使用MFC的人来说并不陌生:你只需建立一个PrintDocument对象并调用其Print()方法就行。PrintDocument对象会调用你建立的事件处理来打印每个页面。打印完所有页面之后,打印处理会设置一个标记并结束打印。

在这个新的例子中,我做了三个地方的改动。首先,我修改了存储方法(storage)以便将被捕获的输入保存在一个ArrayList中,输入的每一行都成为该集合中的一个字符串:

private void grabStream (TextReader inStream){  string str;  while (null != (str = inStream.ReadLine ()))  {    listOStrings.Add (str);    // Pipe it to the output:    System.Console.WriteLine (str);  }}

接下来,我添加了一些命令行选项以使它能够将数据发送到剪贴板中或缺省的打印机上,或者同时发送到这两处(见列表1)。

最后,我写了两个程序来将输出结果打印出来(见列表2)。PrintData()方法用于建立一个PrintDocument对象并启动该打印过程。它还负责为打印页面处理添加事件处理程序。

PrintPage()方法用于打印每个页面。首先,我计算出符合打印页面的行数。字体的GetHeight()方法显示了单独一行的高度,PrintPageEvent的Marginbounds属性显示了每个页面的打印空间有多大。有了这些信息,我便可以简单地绘制出页眉行、页面的每一行以及页脚行。

在打印框架中有一个小问题:PageBounds属性代表的是整个页面的大小,而不是可打印区域的大小。我曾试图将页眉和页脚限定在打印区域以外,但没有成功。在有些打印机上,打印区域以下的地方是不能被打印出来的。所以我只能简单地调整页边距并将页眉和页脚放在打印区域中。


推荐阅读
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • VSCode快速查看函数定义和代码追踪方法详解
    本文详细介绍了在VSCode中快速查看函数定义和代码追踪的方法,包括跳转到定义位置的三种方式和返回跳转前的位置的快捷键。同时,还介绍了代码追踪插件的使用以及对符号跳转的不足之处。文章指出,直接跳转到定义和实现的位置对于程序员来说非常重要,但需要语言本身的支持。以TypeScript为例,按下F12即可跳转到函数的定义处。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
author-avatar
--AppleChan--
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有