掌握.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。他主要用VB、SQL Server和ASP進行開發,但在.NET SDK發布后,他開始沉迷于用C#進行開發。他的Email是mike@sitedev.com。

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97