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. 显示你的报表

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

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

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

// print a red line around the border
// of the page
ev.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. 预览打印

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名字空间的动力。我发现这个名字空间是用来自动生成许多重复性的打印项目(尤其是报表)的最好的方法。尝试用这种方法来完成一个打印任务吧,我敢打赌在用过一次后,你就会立刻将这个方法用于所有的打印任务了。

转载于:https://www.cnblogs.com/hbwinner/archive/2005/08/20/218954.html

运用.NET Framework中的类来创建看上去很专业的报表。相关推荐

  1. 在Asp.net MVC framework中使用扩展方法创建Html Helper

    HtmlHelper提供了一些帮助的方法返回一个字符串来生成html. 在System.Web.Mvc.Html命称空间下有一些表单,控件,局部视图Helper方法.我将创建一个生成标签<inp ...

  2. django中模型类的创建

    ORM框架: 1.一般需要在子应用projects中的models.py定义模型类(相当于数据库中的一张表) 2.必须继承Model或者Model的子类 3.在模型类中定义类属性(必须得为Field子 ...

  3. python中类的创建_如何在python中为类动态创建类方法

    1.基本思想:使用额外的类来保存方法 我找到了一种有意义的工作方式: 首先,我们定义这样的BaseClass: class MethodPatcher: @classmethod def patch( ...

  4. python类的动态方法是什么_如何在python中为类动态创建类方法

    如果我将一个小python程序定义为class a(): def _func(self): return "asdf" # Not sure what to resplace __ ...

  5. Android图片背景朦胧效果,android 图片的模糊化处理,效果类似超级课程表的“我的中心里头像背景,看起来很炫...

    在很多APP里,个人中心是必不可少的,这样就少不了点击头像换头像,设置专属于自己的头像,设置自己的头像后,将自己的头像设为背景,然后模糊化处理成背景图片,这样的效果看起来很炫. 1.将图片转换为Dra ...

  6. Javascript中的类实现

    Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual,不过,Javascript是一门 ...

  7. java中thread类定义在哪个包中,Thread类位于下列哪个包中?()A、javA、ioB、javA、langC、j......

    System和Math类在哪个包中_______.A.java.ioB.java.langC.java.awtD.java.util ObjectInputStream和ObjectOutputStr ...

  8. python 元类的call_【原创】Python 对象创建过程中元类, __new__, __call__, __init__ 的处理...

    原始type: type是最原始的元类,其__call__方法是在你使用" t_class = type(classname_string, base_classes_tuple, attr ...

  9. 详解java中Thread类,线程和进程的基本区别,多线程的好处,线程的五个生命周期,主线程和IDEA创建的Monitor Ctrl-Break守护线程;优雅地终止线程。死锁的产生

    线程:CPU调度的最小单位:线程共享堆内存和方法区,但各自享有栈空间.进程:CPU分配资源的最小单位. 为什么需要多线程:①避免其中一个模块阻塞造成系统假死:②程序异步处理(交替执行),提高程序执行效 ...

  10. C++提高部分_C++类模板中成员函数的创建时机---C++语言工作笔记089

    然后我们再来看类模板中成员函数的创建时机是什么时候 可以看到,普通类中的成员函数一开始就可以创建了, 类模板中的成员函数在调用时才创建. 我们写个例子看看,可以看到我们写了一个Person1类,然后 ...

最新文章

  1. ae制h5文字动画_AE动画在H5设计中的应用研究
  2. win8 开发之旅(13) --webabcd大师,这事你怎么看? 向webabcd大师致敬。
  3. windows下编译openssl
  4. 基于虎书实现LALR(1)分析并生成GLSL编译器前端代码(C#)
  5. springBoot后台发送内容至邮箱
  6. Java中,为什么子类的构造方法中必须调父类的构造方法?
  7. 我去,这几个Linux指令太装B了|动图展示
  8. jsp校园二手交易平台的设计答辩PPT模板
  9. 开源真实场景图像检测数据集汇总
  10. c# 导出Excel
  11. 如何下载使用MSDN原版系统镜像
  12. 2022年信息安全工程师考试知识点:网络安全防御
  13. QQ查询信息接口php源码,免登录获取QQ用户信息API接口源码
  14. 5 月最大的 GameFi 崩溃受害者能否在熊市中生存?| May Monthly Report
  15. 使用 OneDrive 对电脑内的任意文件进行备份
  16. 【excel】QUARTILE.EXC V.S. QUARTILE.INC
  17. Security Warning: The negotiated TLS 1.0 is an insecure protocol and is supported for backward compa
  18. 分享一个特别喜欢的背景图片
  19. keil出现no source“: Error: command-line: #564: cannot open解决方法
  20. 手机LBS位置应用服务盘点

热门文章

  1. iphone储存空间系统怎么清理_iPhone内存总是不够用?那是你不会清理,这样清立马腾出10G空间...
  2. 远程连接ubuntu中mysql的数据库
  3. 计算机考试数据库相关知识点,计算机等级考试四级数据库工程师知识点总结
  4. Java 实现线程的两种方式
  5. Dart基础第11篇:抽象类 多态 以及接口
  6. 浅析Linux设备树dts相关知识
  7. linux系统下安装python3
  8. oracle操作字符串:拼接、替换、截取、查找、长度、判断
  9. C/C++中struct/union/class内存对齐
  10. MPC模型预测控制器——数学推导