65.9K
CodeProject 正在变化。 阅读更多。
Home

《.NET 打印绝对初学者指南》

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (43投票s)

2008年7月1日

CPOL

6分钟阅读

viewsIcon

162475

downloadIcon

3235

介绍并讨论了 .NET Windows Forms 应用程序中用于打印的通用语言运行时框架类。

一切从一张空白纸开始

打印子系统在第一次接触时可能会让人望而生畏。大多数开发人员都不熟悉它,而且它缺乏 Windows Forms Designer 让我们如此熟悉的“拖放代码”体验。然而,.NET Framework 中内置了许多类,可以使整个体验不那么繁琐……

打印系统

虽然在不深入了解打印子系统的情况下,过上幸福而充实的生活是可能的,但如果你对操作系统响应“print”命令时所做的事情有一些了解,编写具有硬拷贝功能的应用程序会稍微容易一些。

应用程序要打印,首先需要知道要打印到哪个打印设备。为此,它会查询后台打印服务器,要求它返回连接设备的列表以及一些基本信息(名称、位置、设备驱动程序等)。

然后,用户选择一个,应用程序获取一个指向该打印机的句柄,通过该句柄查询设备设置(例如其使用的纸张尺寸、分辨率等),然后获取一个设备上下文以绘制第一页。绘制页面通过标准 GDI 绘图命令完成,例如 ExtTextOutRectanglePolygon 等。

页面绘制完成后,应用程序要么通知后台打印系统作业已完成,要么请求另一页。在此阶段,绘制的页面可以通过打印机驱动程序转换为适合打印机的打印机控制语言,然后硬件就可以执行其操作了。

在页面之间,应用程序还可以更改打印机的设置,以便例如,一页可以横向打印,下一页可以纵向打印,等等。

每当应用程序或打印机对打印作业执行操作时,都会引发通知,以便任何监视打印作业的应用程序都可以收到其进度的通知,并且可以将打印文档时发生的任何问题通知用户以进行纠正。

.NET 中打印

.NET 中的打印非常遵循打印系统概述。您主要使用两个框架类来完成所有打印工作,它们位于 System.Drawing.Printing 命名空间中:PageSettings,用于选择纸张尺寸和页面方向等,以及 PrintDocument,用于执行打印操作本身。

可以说,在 .NET 中实现自定义打印操作的最佳方法是创建自己的类,该类包含一个类型为 PrintDocumentprivate 变量,并在该类的事件处理程序中编写代码以执行打印所需的文档。我将按照它们在打印操作生命周期中发生的顺序讨论这些内容。

BeginPrint

当打印作业启动时,会引发 BeginPrint 事件。您应该使用此事件来执行执行打印作业所需的任何设置:将数据指针(如果有)设置为起始位置,并初始化您可能拥有的任何页码变量。

Private Sub PrintDocument_Form_BeginPrint(ByVal sender As Object, _ 
  ByVal e As System.Drawing.Printing.PrintEventArgs)_ 
  Handles PrintDocument_Form.BeginPrint

QueryPageSettings

在打印的每一页之前都会引发 QueryPageSettings 事件。您使用此事件来设置要打印的下一页的任何页面设置。您可以设置方向(横向或纵向)、纸张尺寸、纸张来源等等。在此示例中,我们希望第三页为横向

Private Sub PrintDocument_Form_QueryPageSettings(ByVal sender As Object, _ 
    ByVal e As System.Drawing.Printing.QueryPageSettingsEventArgs) _ 
    Handles PrintDocument_Form.QueryPageSettings

   If _LogicalPageNumber = 3 Then
     e.PageSettings.Landscape = True
   Else
     e.PageSettings.Landscape = False
  End If

End Sub 

PrintPage

页面上的所有实际打印都在 PrintPage 事件处理程序中完成。此事件在打印的每一页上引发,您在此事件处理程序中编写的绘图和文本命令控制页面上打印的内容。

Private Sub PrintDocument_Form_PrintPage(ByVal sender As Object, _ 
   ByVal e As System.Drawing.Printing.PrintPageEventArgs) _ 
   Handles PrintDocument_Form.PrintPage

在每页结束时,您决定是否还有更多页面要打印,将 e.HasMorePages 属性设置为 True,然后 QueryPageSettingsPrintPage 事件将再次触发。重要的是要记住使用相同的事件处理程序,因此如果您想打印不同的页面,您需要跟踪您所在的页面并相应地编写 PrintPage 事件处理程序。

EndPrint

当最后一页打印完毕(并且您已告知打印系统没有更多页面时),会引发 EndPrint 事件。您可以使用此事件处理程序来清理为打印作业创建的任何对象。

提示和技巧

  • 打印中的图形测量单位为毫米的十分之一 - 例如,要打印一个 4 x 5 厘米的矩形,您将使用 e.Graphics.DrawRectangle(Pens.Black, 10, 10, 400, 500)
  • Z 顺序(如果存在)由打印命令的发出顺序定义 - 即,后发出的绘图命令打印在先前的命令之上

工作示例

为了说明这一点,下面是一个“快速而粗糙”的餐厅指南示例。其数据源是一个 XML 数据集,如下所示:

<?xml version="1.0" encoding="utf-8" ?> 
<!-- The list of restaurants -->
 <RestaurantMenu xmlns="http://tempuri.org/RestaurantMenu.xsd">
  <Restaurant>
   <Name>MV Cillairne</Name>
   <Address_1 >
   North Wall Quay
   </Address_1>
   <Address_2 >
   Docklands,
   Dublin 1
   </Address_2>
   <Logo_Image >D:\Users\Duncan\Documents\Presentations\
        Hardcore Hardcopy\RestaurantMenuPrinter\RestaurantMenuPrinter\
        Images\mvcillairne.bmp</Logo_Image>
  </Restaurant>

要打印此数据,我们创建一个继承自 PrintDocument 的新类

Public Class RestaurantDocument
       Inherits System.Drawing.Printing.PrintDocument

要打印此记录集,我们需要一个记录指针(以了解我们在数据集中的位置)和几个用于打印数据的字体。这些将在 BeginPrint 事件中设置并在 PrintPage 事件中使用,因此在类级别声明

    Private _restaurantDataSet As DataSet

    Private _currentPage As Integer = 0
    Private _currentRecord As Integer = 0

    Private _titleFont As Font
    Private _detailFont As Font

并在 BeginPrint 事件中初始化

    ''' <summary>
    ''' The BeginPrint event is raised when the print job is initiated.
    ''' You should use this event to do any setting up that will be 
    ''' required to perform the print job
    ''' </summary>
    Private Sub RestaurantDocument_BeginPrint(ByVal sender As Object, _ 
             ByVal e As System.Drawing.Printing.PrintEventArgs) _ 
                    Handles Me.BeginPrint

        '\\ Create the font objects we are going to print with
        _titleFont = New Font(FontFamily.GenericSerif, 16, _ 
                        FontStyle.Bold, GraphicsUnit.Point)
        _detailFont = New Font(FontFamily.GenericSerif, 9, _ 
                          FontStyle.Regular, GraphicsUnit.Point)

    End Sub

对于餐厅指南中的每一页,我们打印餐厅名称、徽标和地址。文本元素使用 Graphics.DrawString 命令打印,图像使用 Graphics.DrawImage 命令打印

    ''' <summary>
    ''' The PrintPage event is called once for each page to print
    ''' </summary>
    ''' <remarks>
    ''' Set e.HasMorePages to true to print more pages
    ''' </remarks>
    Private Sub RestaurantDocument_PrintPage(ByVal sender As Object, _ 
                ByVal e As System.Drawing.Printing.PrintPageEventArgs) _ 
                     Handles Me.PrintPage

        With _restaurantDataSet.Tables(0).Rows(_currentRecord)
            '\\ Print the restaurant name
            e.Graphics.DrawString(.Item("Name").ToString, _ 
                       _titleFont, Brushes.Black, 20, 20)

            Dim yPos As Single = 100

            '\\ Print the restaurant image
            If (.Item("Logo_Image").ToString <> "") Then
                Dim fiImage As New _ 
                   System.IO.FileInfo(.Item("Logo_Image").ToString)
                Dim img As New Bitmap(fiImage.FullName)
                e.Graphics.DrawImage(img, 20, 60)

                yPos += img.Height
            End If

            '\\ Print the restaurant address details
            e.Graphics.DrawString(.Item("Address_1").ToString, _ 
                        _detailFont, Brushes.Black, 20, yPos)
            yPos += e.Graphics.MeasureString(.Item("Address_1").ToString, _ 
                        _detailFont).Height
            e.Graphics.DrawString(.Item("Address_2").ToString, _ 
                        _detailFont, Brushes.Black, 20, yPos)
            yPos += e.Graphics.MeasureString(.Item("Address_2").ToString,  _ 
                       _detailFont).Height
            e.Graphics.DrawString(.Item("Telephone").ToString, _ 
                       _detailFont, Brushes.Black, 20, yPos)


        End With
        _currentRecord += 1

        If (_currentRecord < _restaurantDataSet.Tables(0).Rows.Count) Then
            e.HasMorePages = True
            _currentPage += 1
        End If

    End Sub

并且,Graphics.MeasureString 用于正确地使地址行相互间隔开。

将打印功能连接到 Windows 窗体

为了打印和预览这个基本文档,我们需要将其连接到一个 Windows 窗体,该窗体上有一个 PrintDialog 和一个 PrintPreviewDialog 组件,以及三个菜单项:打印预览打印文档属性

将这三个元素连接在一起的代码基于窗体代码中 RestaurantDocument 类的 private 实例

Public Class Form1

    Private MyRestaurantDoc As RestaurantDocument
    '--8<--------------

要更改文档设置,我们显示 PrintDialog 对话框

    Private Sub DocumentSettingsToolStripMenuItem_Click(ByVal sender As Object, _ 
           ByVal e As System.EventArgs) _ 
              Handles DocumentSettingsToolStripMenuItem.Click

        If Me.PrintDialog_Restaurant.ShowDialog = Windows.Forms.DialogResult.OK Then
            MyRestaurantDoc.PrinterSettings = Me.PrintDialog_Restaurant.PrinterSettings
        End If

    End Sub

要预览文档,我们需要显示 PrintPreviewDialog 组件对话框

    Private Sub PrintPreviewDialog_Restaurant_Click(ByVal sender As Object, _ 
                    ByVal e As System.EventArgs) _ 
                      Handles PrintPreviewToolStripMenuItem.Click
        PrintPreviewDialog_Restaurant.Document = MyRestaurantDoc
        PrintPreviewDialog_Restaurant.ShowDialog()
    End Sub

最后(也是最简单的),将文档打印到打印机

    Private Sub PrintToolStripMenuItem_Click(ByVal sender As Object, _ 
                ByVal e As System.EventArgs) _ 
                   Handles PrintToolStripMenuItem.Click
        MyRestaurantDoc.Print()
    End Sub

我希望这篇文章能为您提供足够的见解,让您能够接近 .NET 打印子系统。一旦您做到了,您就会发现它是框架中一个非常强大的部分,将有助于制作功能更全面的 Windows 应用程序。

更新:我已经添加了从我提取这篇文章的电子书的全文,因为我不太可能写到第二部分。

© . All rights reserved.