掌握.NET中的日常打印(2)

发表于:2007-05-25来源:作者:点击数: 标签:代码掌握.NET日常打印
写代码 Rectangle对象提供了最快、最简单的方法来将你的页面设计转换成代码。这些对象可以让你几乎很准确地在屏幕上模拟你的设计。在页面上定义一个区域,指定坐标(有一定的高度和宽度)。Rectangle对象可以给你提供指定的点,在这个点上,你可以用Graphics

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

clearcase/" target="_blank" >cc>
// 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(见下)。

C#:用P/Invoke得到页边距

列表 3.

.NET Framework不提供方法得到一台打印机的hard margins,所以你需要用P/Invoke并调用Win32的GetDeviceCaps函数。这个类的这个方法中包含一个设备驱动程序代码(hDc),然后用你需要的信息来填充类成员。

[DllImport("gdi32.dll")]
private static extern Int16 GetDeviceCaps([In] 
   [MarshalAs 
   (UnmanagedType.U4)] int hDc, [In] [MarshalAs 
   (UnmanagedType.U2)] Int16 funct);
private float _leftMargin = 0;
private float _topMargin = 0;
private float _rightMargin = 0;
private float _bottomMargin = 0;
const short HORZSIZE      = 4;
const short VERTSIZE      = 6;
const short HORZRES       = 8;
const short VERTRES       = 10;
const short PHYSICALOFFSETX = 112;
const short PHYSICALOFFSETY = 113;
public marginInfo(int deviceHandle) {
   float offx = Convert.ToSingle(
      GetDeviceCaps(deviceHandle, 
      PHYSICALOFFSETX));
   float offy = Convert.ToSingle(
      GetDeviceCaps(deviceHandle, 
      PHYSICALOFFSETY));
   float resx = Convert.ToSingle(
      GetDeviceCaps(deviceHandle, HORZRES));
   float resy = Convert.ToSingle(
      GetDeviceCaps(deviceHandle, VERTRES));
   float hsz = Convert.ToSingle(
      GetDeviceCaps(deviceHandle, HORZSIZE))/25.4f;
   float vsz = Convert.ToSingle(
      GetDeviceCaps(deviceHandle,VERTSIZE))/25.4f;
   float ppix = resx/hsz;
   float ppiy = resy/vsz;
   _leftMargin  = (offx/ppix) * 100.0f;
   _topMargin   = (offy/ppix) * 100.0f;
   _bottomMargin  = _topMargin + (vsz * 100.0f);
   _rightMargin  = _leftMargin + (hsz * 100.0f);
}

在得到hard margins后,你就可以开始创建Rectangle对象并在你的报表上打印信息了(见下)。

C#:设计报表的版面

列表 4.

任何报表的基本格式至少必须包含一个页眉、正文和页脚区域。运用Rectangle对象可以很容易地设计你的报表版面。通过添加更多的Rectangle对象,你就可以提供这些报表的复杂程度。

// create the header
int headerHeight = 
   hf.GetHeight(ev.Graphics);
RectangleF header = new 
   RectangleF(leftMargin, topMargin, 
      pageWidth, headerHeight);

// create the footer
int bodyFontHeight = 
   bodyFont.GetHeight(ev.Graphics);
RectangleF footer = new  
   RectangleF(leftMargin, body.Bottom, 
      pageWidth, bodyFontHeight);
// create the body section
RectangleF body = new 
   RectangleF(leftMargin, header.Bottom, 
      pageWidth, pageHeight - 
      bodyFontHeight);

现在你已经创建了长方形边框(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方法将文本放置在了正确的位置上。通过用你选择的字体的高度乘以你用来跟踪已经打印了多少行的计数器,你就可以在每打印一行时计算下一行的位置了。然后为顶部的页边距添加值(见下)。

C#:PrintPage是打印的关键

列表 5.

当运用System.Drawing.Printing对象来打印时, PrintPage是你要用到的主要的事件。该事件为每个页面触发一次,直到你将ev.HasMorePages的值设置成false。用来得到hard margins的代码弥补了.NET Framework中的不足。

private void doc_PrintPage(object sender, 
   System.Drawing.Printing.PrintPageEventArgs ev) {
   _currentPage++;
   String headerText = "Northwinds Customer Contacts";
   IntPtr hDc = ev.Graphics.GetHdc();
   ev.Graphics.ReleaseHdc(hDc);
   marginInfo mi = new marginInfo(hDc.ToInt32());
   // take the hard margins into account?
   float leftMargin = ev.MarginBounds.Left - mi.Left;
   float rightMargin = ev.MarginBounds.Right;
   float topMargin = ev.MarginBounds.Top - mi.Left;
   float bottomMargin = ev.MarginBounds.Bottom;
   float pageHeight = bottomMargin - topMargin;
   float pageWidth = rightMargin - leftMargin;
   float headerHeight = 
   headerFont.GetHeight(ev.Graphics);
   float footerHeight = bodyFont.GetHeight(ev.Graphics);
   // report header
   RectangleF ReportheaderR = new RectangleF(leftMargin, 
      topMargin, pageWidth, headerHeight);
   // report body
   RectangleF bodyR = new RectangleF(leftMargin, 
      ReportheaderR.Bottom, pageWidth, pageHeight - 
      ReportheaderR.Height - footerHeight);            
   // report footer
   RectangleF ReportfooterR = new RectangleF(leftMargin, 
      bodyR.Bottom, pageWidth, footerHeight * 2);
   // results of using the Split function on the text
   String[] el;
   // a line of text from our file
   string text = "";
   // print the header once per page
   centerText(ev.Graphics, headerText, headerFont, 
      defaultBrush, ReportheaderR);
   // the header is equal to 2 normal lines
   int currentLine = 2;
   // how many lines can we fit on a page?              
   int linesPerPage = Convert.ToInt32(bodyR.Height / 
      bodyFont.GetHeight(ev.Graphics)) - 1;
   float bodyFontHeight = 
      bodyFont.GetHeight(ev.Graphics);
   float currentY;
   // Print each line of the file.
   while(currentLine < linesPerPage && 
   ((text=data.ReadLine()) != null)) {
      el = text.Split(',');
      currentY = getCurrentY(currentLine, topMargin, 
         bodyFontHeight);
      ev.Graphics.DrawString(el[0], bodyFont, 
         defaultBrush, bodyR.Left, currentY);
      currentLine++;
      currentY = getCurrentY(currentLine, topMargin, 
         bodyFontHeight);
      ev.Graphics.DrawString(el[1], 
         bodyFont, defaultBrush, bodyR.Left + 20, 
         currentY);
      currentLine++;
      currentY = getCurrentY(currentLine, topMargin, 
         bodyFontHeight);
      ev.Graphics.DrawString("Phone: " + el[2], 
         bodyFont, defaultBrush, bodyR.Left + 20, 
         currentY);
      currentLine++;
      currentY = getCurrentY(currentLine, topMargin, 
         bodyFontHeight);
      ev.Graphics.DrawString("Fax: " + el[3],
         bodyFont,defaultBrush, bodyR.Left + 20, 
         currentY);
      currentLine++;
      currentY = getCurrentY(currentLine, topMargin, 
         bodyFontHeight);
      ev.Graphics.DrawLine(Pens.Black, leftMargin, 
         currentY, ev.MarginBounds.Right, currentY);
   }
   // page number
   centerText(ev.Graphics, "Page " + 
      currentPage.ToString(), bodyFont, 
      defaultBrush, ReportfooterR);
   if (text != null) {
      ev.HasMorePages = true;
   } else {
      // no more pages to print
      ev.HasMorePages = false;
   }
}
private float getCurrentY(int currentLine, float 
   topMargin, float fontHeight) {
   return topMargin + (currentLine * fontHeight);
}
private void centerText(Graphics g, 
   string t, Font f, Brush b, RectangleF 
   rect) {
   StringFormat sf = new StringFormat();
   sf.Alignment = 
      StringAlignment.Center;
   g.DrawString(t, f, b, rect, sf);
}

你运用的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. javascript:openWindowRes('VS/2002_11/xml2html.asp?xmlfile=NetPrinting/Figure2.xml&xslfile=../../include/xsl/Figure.xsl');">预览打印
PrintPreviewControl和PrintPreviewDialog类在Printing名字空间中提供了很好的功能。PrintPreviewDialog封装了PrintPreviewControl类,并提供了一个很好的用户界面,用来在页面间导航、改变缩放比例、选择一次可以预览的页数(见下)。


图 2. 预览打印

运用Printing名字空间中的类来创建一个报表可能会很麻烦,但最终结果是值得你这么做的。它会成为你打印日常报表的一个缺省的方法。


你可以用PrintPreviewControl来创建一个与你的应用程序其它部分相一致的打印预览窗口。一旦你定义了你的PrintDocument,并为必需的打印事件编写了代码后,添加打印预览就很简单了:

private void preview_Click(object 
   sender, System.EventArgs e) {
   PrintPreviewDialog pd = new 
      PrintPreviewDialog();
   pd.Document = doc;
   pd.ShowDialog();
}

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


关于作者:
Michael Eaton是位专攻Microsoft技术的独立顾问。自1994年来,他一直在从事软件开发工作,并于1995年获得MCSD。他主要用VBSQL Server和ASP进行开发,但在.NET SDK发布后,他开始沉迷于用C#进行开发。他的Email是mike@sitedev.com

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)