Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 24 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
24
Dung lượng
597,82 KB
Nội dung
Printing Although many people try to make their offices as paperless as possible, some printing is inevitable. Whether you print documents to read while you are away from your computer, to put in printed reports, or to mail to customers, eventually you will probably need to print something. Over the past few years, printing has gone from a tedious (but intuitive) process in Visual Basic 6 to a tedious (and non-intuitive) process in Visual Basic .NET. Visual Basic 6 used a procedural model for printing. You used the Printer object’s properties and methods to generate output. You would call methods to write text, draw graphics, and generate new pages. Visual Basic .NET takes an event-driven approach. Here, you start the printing process and then wait for events. A PrintDocument object raises events and asks you to generate the document’s pages. The event handler includes a Graphics object, and you use its properties and methods to draw whatever output you want to print. I find this approach rather unnatural, so I also devised a technique that uses metafiles to imple- ment a more intuitive, procedural approach. This method lets you draw the entire printout all at once, rather than responding to events. It even lets you go back and modify earlier pages after you have drawn those that come later. For example, it lets you add page numbers of the form “Page 1 of 10” even if you don’t know how many pages there are until after you have generated them. This chapter describes Visual Basic .NET’s basic event-driven model. It then explains the metafile- based, more intuitive procedural approach. 27_053416 ch20.qxd 1/2/07 6:36 PM Page 511 Event-Driven Printing The basic idea behind event-driven printing is to create a PrintDocument object and tell it to start print- ing. Then you respond to the object’s events to generate the pages of printout as the object needs them. The following list describes the object’s key events: ❑ BeginPrint — This event occurs when the PrintDocument object is about to start printing the document. The code can use this event to prepare for printing by opening data sources, building fonts and brushes, setting a page number variable to 1, and so forth. ❑ QueryPageSettings — This event occurs before the PrintDocument object starts printing a page. It gives the program a chance to change the page’s settings before printing on it. For example, if you are printing a booklet, you might change the margins on odd and even num- bered pages to allow a gutter on the side where the binding will go. ❑ PrintPage — This event occurs when the PrintDocument object needs to generate a printed page. The e.Graphics parameter gives the code a Graphics object on which to draw. The code should set the e.HasMorePages parameter to True if there are more pages to print or False if the program has printed the final page. ❑ EndPrint — This event occurs when the PrintDocument object is finished printing the last page. The program should perform cleanup tasks such as shutting down data sources, and dis- posing of fonts and brushes. Often, this event handler undoes actions taken in the BeginPrint event handler. Example program SimplePrint (available for download at www.vb-helper.com/one_on_one.htm) prints and provides a print preview for a very simple document. It is stripped down to the bare mini- mum so that you can see how PrintDocument events work without becoming distracted by graphics details. The program’s form contains two non-visible components. The pdocSimple component is a Print Document object. It provides the events that generate the printout. The pviewSimple control is a PrintPreviewDialog control. It displays a preview of the printout. The following code shows how the program works: Public Class Form1 ‘ Display a print preview. Private Sub btnPreview_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnPreview.Click ‘ Set the print preview control’s PrintDocument object. pviewSimple.Document = pdocSimple ‘ Display the preview. ‘ The PrintDocument object does the real work. pviewSimple.ShowDialog() End Sub ‘ Print. Private Sub btnPrint_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnPrint.Click ‘ Use the PrintDocument to print. pdocSimple.Print() 512 Part IV: Specific Techniques 27_053416 ch20.qxd 1/2/07 6:36 PM Page 512 End Sub #Region “Printing Code” Private m_NextPage As Integer Private m_Font As Font ‘ Prepare to print. Private Sub pdlgWrap_BeginPrint(ByVal sender As Object, _ ByVal e As System.Drawing.Printing.PrintEventArgs) _ Handles pdocSimple.BeginPrint ‘ Set the next page number. m_NextPage = 1 ‘ Create the font we will use. m_Font = New Font(“Times New Roman”, 200, FontStyle.Bold, _ GraphicsUnit.Point) End Sub ‘ Clean up. Private Sub pdocSimple_EndPrint(ByVal sender As Object, _ ByVal e As System.Drawing.Printing.PrintEventArgs) _ Handles pdocSimple.EndPrint ‘ Dispose of the font. m_Font.Dispose() m_Font = Nothing End Sub ‘ The PrintDocument needs a page. Print the next one. Private Sub pdlgWrap_PrintPage(ByVal sender As System.Object, _ ByVal e As System.Drawing.Printing.PrintPageEventArgs) _ Handles pdocSimple.PrintPage ‘ Draw the page number. e.Graphics.DrawString(m_NextPage.ToString, _ m_Font, Brushes.Black, _ e.MarginBounds.X, e.MarginBounds.Y) m_NextPage += 1 ‘ Draw 5 pages. e.HasMorePages = (m_NextPage <= 5) End Sub #End Region ‘ Printing Code End Class When you click the Preview button, the program sets the pviewSimple component’s Document prop- erty to pdocSimple, and then calls the dialog’s ShowDialog method. The dialog uses the pdocSimple object to generate the printout as necessary. When you click the Print button, the program directly calls the pdocSimple object’s Print method to make the object immediately generate and print the printout. The printing section of the code includes some module-level variables that control printing and the PrintDocument object’s event handlers. The m_NextPage variable tracks the number of the next page to print. The m_Font variable stores the font that the printing code uses. 513 Chapter 20: Printing 27_053416 ch20.qxd 1/2/07 6:36 PM Page 513 When the PrintDocument’s BeginPrint event fires, the event handler sets m_NextPage to 1 and cre- ates a very large font to use when printing. The PrintDocument’s EndPrint event handler cleans up by disposing of the font. The PrintPage event handler contains the most interesting code, although it’s quite simple in this example. The code uses the e.Graphics object’s DrawString method to display the current page num- ber using the large font. It then increments the page number and sets e.HasMorePages to True if the program has not yet printed five pages. After the PrintPage event handler exits, the PrintDocument object finishes generating the page and raises the event again if e.HasMorePages indicates that it must print more pages. Figure 20-1 shows the PrintPreviewDialog displaying all five of the document’s pages at the same time. Figure 20-1: The PrintPreviewDialog control can display one, two, three, four, or six pages at a time. Here the dialog would display six pages, but the document has only five. The SimplePrint example is easy enough to understand because it doesn’t do much. Unfortunately printing a less-trivial document can be much more complicated. The following sections explain how you can handle other tasks that are more common and complex. Printing Forms Visual Basic 6 forms have a PrintForm method that immediately sends a bitmapped image of the form to the printer. The result is fairly grainy because the screen resolution is far lower than the printer’s resolu- tion, but this method is so simple that it is used heavily. It’s a great way to produce a simple snapshot of a form, or to provide low-resolution printing until you have time to implement a higher-resolution version. 514 Part IV: Specific Techniques 27_053416 ch20.qxd 1/2/07 6:36 PM Page 514 Here, “low-resolution” is a relative term. A typical monitor might provide between 72 and 130 pixels per inch (PPI). That’s fine for a brightly lighted screen, but printers typically have much higher resolutions ranging from 300 to 1200 dots per inch (DPI). High-end inkjet printers have resolutions up to 9600 DPI. The difference in resolution between monitors and printers means that Visual Basic 6’s PrintForm method draws each screen pixel as a tiny square on the printer. The result looks a bit blocky. Unfortunately, Visual Basic .NET’s forms don’t have a PrintForm method, so you have to write your own. The first step is to use code similar to the following to get a bitmap representing the form’s image: Module PrintFormCode ‘ Return the form’s image. Public Function GetFormImage(ByVal frm As Form, _ ByVal include_borders As Boolean) As Bitmap ‘ Get the whole form. Dim wid, hgt As Integer wid = frm.Width hgt = frm.Height Dim bm As Bitmap = New Bitmap(wid, hgt) frm.DrawToBitmap(bm, New Rectangle(0, 0, wid, hgt)) ‘ If we want the borders, return this bitmap. If include_borders Then Return bm ‘ Make a new bitmap to hold the image without the borders. Dim new_wid As Integer = frm.ClientSize.Width Dim new_hgt As Integer = frm.ClientSize.Height Dim new_bm As New Bitmap(new_wid, new_hgt) Dim new_gr As Graphics = Graphics.FromImage(new_bm) ‘ Get the client area’s origin in screen coordinates. Dim origin As New Point(0, 0) origin = frm.PointToScreen(origin) ‘ See how far this is from the form’s upper left corner. Dim x As Integer = origin.X - frm.Left Dim y As Integer = origin.Y - frm.Top ‘ Copy the form’s client area image into the new bitmap. new_gr.DrawImage(bm, _ New RectangleF(0, 0, new_wid, new_hgt), _ New RectangleF(x, y, new_wid, new_hgt), _ GraphicsUnit.Pixel) ‘ Return the client area image. Return new_bm End Function End Module The code starts by creating a bitmap big enough to hold the form’s image. It then calls the form’s DrawToBitmap method to make it draw itself into the bitmap. If the function’s include_borders parameter is true, the function returns this bitmap. If the include_borders parameter is false, the code makes a new bitmap that is the right size to hold the form’s client area (the area inside the borders). It also makes a Graphics object for use when draw- ing on the smaller bitmap. 515 Chapter 20: Printing 27_053416 ch20.qxd 1/2/07 6:36 PM Page 515 Next, the code makes a Point object holding the coordinates of the upper-left corner of the form’s client area. It uses the form’s PointToScreen method to translate that point into screen coordinates. The form’s Left and Top properties give the coordinates of the form’s upper-left corner in screen coordi- nates, so the difference between those values and the translated point gives the distance from the upper- left corner of the form to the upper-left corner of its client area. The code uses those values to copy the part of the form’s image corresponding to the client area into the smaller bitmap. It then returns the result. Example program PrintFormImage (available for download at www.vb-helper.com/one_on_one.htm) uses function GetFormImage to get an image of the form. It then displays it in a PrintPreviewDialog or prints it directly. Checkboxes enable you to determine whether the program prints the image with or without borders, at normal size or scaled to fit the page, and in portrait or landscape orientation. Figure 20-2 shows the program on the left. The Print preview dialog on the right displays the form’s image with borders scaled to fill the page in landscape orientation. Program PrintFormImage prepares its PrintDocument object and sets the PrintPreviewDialog object’s Document property the same way program SimplePrint did in the previous section, “Event- Driven Printing.” Figure 20-2: Program PrintFormImage can print an image with or without borders, at normal size or scaled to fit the page, and in portrait or landscape orientation. The following code shows the PrintDocument event handlers that this program uses to display the form’s image: ‘ Set landscape or portrait orientation. Private Sub pdocForm_QueryPageSettings(ByVal sender As Object, _ ByVal e As System.Drawing.Printing.QueryPageSettingsEventArgs) _ Handles pdocForm.QueryPageSettings If cboLandscape.Checked Then ‘ Print in landscape. e.PageSettings.Landscape = True 516 Part IV: Specific Techniques 27_053416 ch20.qxd 1/2/07 6:36 PM Page 516 Else ‘ Print in portrait. e.PageSettings.Landscape = False End If End Sub ‘ Print the form’s image. Private Sub pdocForm_PrintPage(ByVal sender As Object, _ ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles pdocForm.PrintPage ‘ Get an image of the form. Dim bm As Bitmap = GetFormImage(Me, cboIncludeBorders.Checked) ‘ Prepare the Graphics object. PrepareGraphics(e, bm) ‘ Draw the image at the origin. e.Graphics.DrawImage(bm, 0, 0, bm.Width, bm.Height) ‘ There’s only one page. e.HasMorePages = False End Sub ‘ Scale and translate the Graphics object appropriately. Private Sub PrepareGraphics(_ ByVal e As System.Drawing.Printing.PrintPageEventArgs, ByVal bm As Bitmap) ‘ See how big the resulting image should be. Dim the_scale As Single If cboFitPage.Checked Then ‘ Scale to fit. Dim aspect1 As Double = bm.Width / bm.Height Dim aspect2 As Double = e.MarginBounds.Width / e.MarginBounds.Height If aspect1 > aspect2 Then ‘ The image is relatively wider and thinner than the page. ‘ Make it fit the page’s width. the_scale = CSng(e.MarginBounds.Width / bm.Width) Else ‘ The image is relatively shorter and taller than the page. ‘ Make it fit the page’s height. the_scale = CSng(e.MarginBounds.Height / bm.Height) End If ‘ Scale. e.Graphics.ScaleTransform(the_scale, the_scale) Else ‘ Use the image at its normal scale. the_scale = 1 End If ‘ Translate to center the image. Dim wid As Single = bm.Width * the_scale Dim hgt As Single = bm.Height * the_scale Dim dx As Single = e.MarginBounds.X + (e.MarginBounds.Width - wid) / 2 Dim dy As Single = e.MarginBounds.Y + (e.MarginBounds.Height - hgt) / 2 e.Graphics.TranslateTransform(dx, dy, Drawing2D.MatrixOrder.Append) End Sub 517 Chapter 20: Printing 27_053416 ch20.qxd 1/2/07 6:36 PM Page 517 The QueryPageSettings event handler checks the program’s cboLandscape combo box. If the user has checked this box, the code sets the PageSetting object’s Landscape property to True to make the printer use a landscape orientation. This also adjusts the PrintDocument’s margins so they work with the landscape setting. The PrintPage event handler prints the form’s image. It uses function GetFormImage to get a bitmap containing the form’s image. It then calls subroutine PrepareGraphics to prepare the Graphics object to display the image properly scaled and centered. The routine uses the e.Graphics object’s DrawImage method to draw the form’s image at the position (0, 0) and sets e.HasMorePages to False to indicate that there are no more pages. Subroutine PrepareGraphics starts by determining how big the form’s image will be. If the Fit Page combo box is checked, it compares the form’s width-to-height ratio with the printed page’s width-to-height ratio. If the form’s ratio is greater than the page’s ratio, the form image is rela- tively wider and shorter than the page, so the program picks a scale factor that makes the image fill the page horizontally. If the form’s ratio is smaller than the page’s ratio, the form image is relatively taller and thinner than the page, so the program picks a scale factor that makes the image fill the page verti- cally. The program then calls the e.Graphics object’s ScaleTransform method to scale the result. When the program draws on the object, everything is scaled by this amount. The result is that the image will be scaled to fill the page. After calculating the scale to use, the program determines the image’s final size. It then calculates the translation it must use to center the scaled image on the form. It calls the e.Graphics object’s TranslateTransform method to make this translation. Now, when the PrintPage event handler draws the image at position ( 0, 0), the image is scaled appropriately and translated, so it is centered. Wrapping Text Printing an image of a form is a fairly common task. Another everyday task is printing text. The SimplePrint example earlier in this chapter showed that printing a few numbers or letters is easy. Printing a lot of text that must wrap across pages is harder. Example program PrintWrap, which is available for download at www.vb-helper.com/one_on_one.htm, prints text, wrapping lines at the margins and starting new pages as needed. The program uses the following ParagraphInfo class to store information about paragraphs that it should print. The class’s FontNumber property determines which of three program-defined fonts to use for each paragraph. The Indent property gives the amount to increase the paragraph’s left margin. SpaceAfter indicates the amount of vertical space that should be added after the paragraph. Finally, the Text property gives the paragraph’s text. Public Class ParagraphInfo Public FontNumber As Integer Public Indent As Integer Public SpaceAfter As Integer Public Text As String Public Sub New(ByVal new_FontNumber As Integer, ByVal new_Indent As Integer, _ ByVal new_SpaceAfter As Integer, ByVal new_Text As String) 518 Part IV: Specific Techniques 27_053416 ch20.qxd 1/2/07 6:36 PM Page 518 FontNumber = new_FontNumber Indent = new_Indent SpaceAfter = new_SpaceAfter Text = new_Text End Sub End Class The following code shows the PrintDocument’s BeginPrint and EndPrint event handlers: ‘ The lines we will print. Private m_Paragraphs As List(Of ParagraphInfo) ‘ The number of the next page we will print. Private m_NextPage As Integer ‘ The distance of the page number from the ‘ top and left edges of the margin area. Private Const PAGE_NUM_MARGIN_X As Integer = 50 Private Const PAGE_NUM_MARGIN_Y As Integer = 50 ‘ Fonts we use while printing. Private m_Fonts(0 To 2) As Font ‘ Prepare to print. Private Sub pdlgWrap_BeginPrint(ByVal sender As Object, _ ByVal e As System.Drawing.Printing.PrintEventArgs) Handles pdocWrapped.BeginPrint ‘ Create the text to print. m_Paragraphs = New List(Of ParagraphInfo) m_Paragraphs.Add(New ParagraphInfo(0, 20, 20, _ “19. Splash Screens”)) m_Paragraphs.Add(New ParagraphInfo(1, 0, 20, _ “The first thing a user sees of an application is its splash screen ”)) Other lines omitted ‘ Make the fonts we need. m_Fonts(0) = New Font(“Times New Roman”, 22, FontStyle.Bold, _ GraphicsUnit.Point) m_Fonts(1) = New Font(“Times New Roman”, 14, FontStyle.Regular, _ GraphicsUnit.Point) m_Fonts(2) = New Font(“Courier New”, 10, FontStyle.Regular, _ GraphicsUnit.Point) ‘ Set the next page number. m_NextPage = 1 End Sub ‘ Clean up. Private Sub pdlgWrap_EndPrint(ByVal sender As Object, _ ByVal e As System.Drawing.Printing.PrintEventArgs) Handles pdocWrapped.EndPrint ‘ Dispose of the printing fonts. For i As Integer = 0 To 2 m_Fonts(i).Dispose() m_Fonts(i) = Nothing Next i End Sub 519 Chapter 20: Printing 27_053416 ch20.qxd 1/2/07 6:36 PM Page 519 Variable m_Paragraphs holds a list of Paragraph objects describing the text to print. Variable m_NextPage tracks the current page number. The m_Fonts array contains the three fonts that the pro- gram will use to print. The BeginPrint event handler adds a series of ParagraphInfo objects to the m_Paragraphs list. It then creates three fonts: a large heading font, a smaller text body font, and a code font. It sets m_NextPage to 1 and exits. The EndPrint event handler simply disposes of the fonts. The following code shows the PrintDocument’s PrintPage event handler: ‘ The PrintDocument needs a page. Print the next one. Private Sub pdlgWrap_PrintPage(ByVal sender As System.Object, _ ByVal e As System.Drawing.Printing.PrintPageEventArgs) _ Handles pdocWrapped.PrintPage ‘ Print the page number. Dim x As Single = e.MarginBounds.Right + PAGE_NUM_MARGIN_X Dim y As Single = e.MarginBounds.Top - PAGE_NUM_MARGIN_Y Dim string_format As New StringFormat string_format.Alignment = StringAlignment.Center string_format.LineAlignment = StringAlignment.Center e.Graphics.DrawString(m_NextPage.ToString(), _ m_Fonts(1), Brushes.Black, x, y, string_format) m_NextPage += 1 ‘ Start at the top. y = e.MarginBounds.Top ‘ Loop while we have paragraphs. Do While m_Paragraphs.Count > 0 ‘ Get the next piece of text we need to print. Dim para As ParagraphInfo = m_Paragraphs(0) Dim txt As String = para.Text ‘ See how much room we have. Dim layout_area As New SizeF( _ e.MarginBounds.Width - para.Indent, _ e.MarginBounds.Bottom - y) ‘ Make the height at least 1 to avoid confusing MeasureString If layout_area.Height < 1 Then layout_area.Height = 1 ‘ See how much of the text will fit. Dim characters_fitted As Integer Dim lines_filled As Integer string_format.Alignment = StringAlignment.Near string_format.LineAlignment = StringAlignment.Near string_format.Trimming = StringTrimming.Word string_format.FormatFlags = StringFormatFlags.LineLimit Dim text_size As SizeF = e.Graphics.MeasureString(txt, _ m_Fonts(para.FontNumber), layout_area, string_format, _ 520 Part IV: Specific Techniques 27_053416 ch20.qxd 1/2/07 6:36 PM Page 520 [...]... text, a heading, and some code text that is indented and drawn in a different font Figure 2 0-3 : Program PrintWrap wraps text across lines and pages Flowing Text Program PrintWrap described in the previous section is fairly useful if you only need to display text A common task that this program cannot handle is displaying text interspersed with pictures Program PrintFlow, shown in Figure 2 0-4 , draws text... page Instead, it ends the PrintPage event handler and resumes work when the next PrintPage event fires However, by using metafiles, you can make Visual Basic NET print procedurally A metafile is a file that contains a series of drawing commands The basic approach is to draw the document in a series of metafiles and then render them in the PrintPage event handler The following code shows how example... in this chapter show how to print form images and simple text in Visual Basic NET They show how to wrap text across lines and pages, and how to flow text around pictures Program PrintMetafile shows how to print procedurally, rather than in an event-driven way However, this is far from the end of the story for printing Even when printing text alone, you can add features for hyphenation, widow and orphan... page_settings.Bounds.Top + page_settings.Margins.Top Dim xmax As Single = page_settings.Bounds.Right - page_settings.Margins.Right Dim ymax As Single = page_settings.Bounds.Bottom - page_settings.Margins.Bottom Dim wid As Single = xmax - xmin Dim hgt As Single = ymax - ymin Dim margin_rect As New RectangleF(xmin, ymin, xmax - xmin, ymax - ymin) ‘ Make a list of Graphics objects for the metafiles Dim grs As New List(Of... Font(Me.Font.FontFamily, 70, FontStyle.Bold, _ GraphicsUnit.Pixel) Dim num_pages As Integer = m_Pages.Count Dim margin_rectf As New RectangleF(xmin, ymin, xmax - xmin, ymax - ymin) For i As Integer = 0 To m_Pages.Count - 1 530 27_053416 ch20.qxd 1/2/07 6:36 PM Page 531 Chapter 20: Printing grs(i).DrawString(“Page “ & i + 1 & “ of “ & num_pages, _ big_font, Brushes.Black, _ margin_rectf, string_format) grs(i).Dispose()... applications, including CorelDRAW! and Windows Picture and Fax Viewer You can even insert them into Microsoft Word documents 531 27_053416 ch20.qxd 1/2/07 6:36 PM Page 532 Part IV: Specific Techniques Figure 2 0-5 : Program PrintMetafile draws pages onto metafiles before printing them Microsoft Paint can open metafiles, but the results are usually not very good Microsoft Paint isn’t really designed to work with metafiles... The event handler sets e.HasMorePages to True if there are more paragraphs to print, and then exits Figure 2 0-3 shows program PrintWrap’s print preview dialog displaying the first three pages of the document (You can download this example at www.vb-helper.com/one_on_one.htm.) Though you can’t read the text, you can see that the first page contains a heading, some body text, a second heading, and more... Word handle the typography That will be a lot faster and easier than trying to rewrite Word yourself from scratch 532 27_053416 ch20.qxd 1/2/07 6:36 PM Page 533 Chapter 20: Printing When a program prints a document, it actually only adds the document to a queue for printing later The printing occurs asynchronously, so the program can move on to do other things without waiting for the queue to empty and. .. that this program cannot handle is displaying text interspersed with pictures Program PrintFlow, shown in Figure 2 0-4 , draws text that flows around pictures 522 27_053416 ch20.qxd 1/2/07 6:36 PM Page 523 Chapter 20: Printing Figure 2 0-4 : Program PrintFlow draws text that flows around pictures This program stores paragraph information in ParagraphInfo objects in the same way as program PrintWrap described... text, until it runs out of rectangles or text in the current paragraph Now that you understand the basic idea, you can look at the code This program’s BeginPrint and EndPrint event handlers are similar to those used by program PrintWrap, so they are not shown here The following code shows the PrintPage event handler, which does the most interesting work: ‘ The PrintDocument needs a page Print the next . process in Visual Basic 6 to a tedious (and non-intuitive) process in Visual Basic .NET. Visual Basic 6 used a procedural model for printing. You used the Printer object’s properties and methods. them. This chapter describes Visual Basic .NET’s basic event-driven model. It then explains the metafile- based, more intuitive procedural approach. 27_053416 ch20.qxd 1/2/07 6:36 PM Page 511 Event-Driven. graphics, and generate new pages. Visual Basic .NET takes an event-driven approach. Here, you start the printing process and then wait for events. A PrintDocument object raises events and asks