A BOOKLET EXAMPLE

The UsePrintPreviewDialog example program described earlier draws some text centered on the program’s three pages of printout. This section describes a more useful example that prints a long series of paragraphs that may each use a different font size.

The PrintBooklet example program, which is available for download on the book’s website, figures out how to break the text into pages. It assumes that you will print the pages double-sided and then later staple the results into a booklet. To allow extra room for the staples, the program adds a gutter to the margin of each page on the side where the staples will be. The program assumes that you will place the first page on the outside of the booklet, so it adds the gutter to the left margin on odd-numbered pages and to the right margin on even-numbered pages. Finally, the program displays a page number in the upper corner opposite the gutter.

In addition to demonstrating event handlers for the PrintDocument class’s events, this example shows how to use StringFormat objects to align text and break lines at word boundaries, wrap text within a target rectangle, and measure text to see how much will fit in a target rectangle.

Figure 27-4 shows the PrintBooklet program’s print preview dialog box, so you can understand the goals. If you look closely, you can see that the left margins on the first and third pages and the right margin on the second page are enlarged to allow room for the gutter. (Imagine the second page printed on the back of the first, so their gutters lie on the same edge of the paper.) You can also see that the page numbers are in the upper corner on the side that doesn’t have the gutter.

FIGURE 27-4: This preview shows text broken across pages with a gutter and displaying page numbers along the outside edges.

image

The program’s Print Preview, Print Dialog, and Print Now buttons work much as the UsePrintPreviewDialog program’s buttons do, displaying the appropriate dialog boxes or calling the PrintDocument object’s Print method. The most interesting differences between this program and the UsePrintPreviewDialog program are in how this program stores its text to print and how it generates pages of printout.

The program uses the following ParagraphInfo structure to store information about the text it will print:

' Information about the paragraphs to print.
Private Structure ParagraphInfo
    Public FontSize As Integer
    Public Text As String
    Public Sub New(font_size As Integer, txt As String)
        FontSize = font_size
        Text = txt
    End Sub
End Structure

The following code shows how the program prepares the text it will print:

 
' The PrintDocument.
Private WithEvents MyPrintDocument As New PrintDocument
 
' The paragraphs.
Private AllParagraphs As List(Of ParagraphInfo)
Private ParagraphsToPrint As List(Of ParagraphInfo)
Private PagesPrinted As Integer
 
' Load the paragraph info.
Private Sub Form1_Load() Handles MyBase.Load
    ' Attach the PrintDocument to the 
    ' PrintDialog and PrintPreviewDialog.
    dlgPrint.Document = MyPrintDocument
    dlgPrintPreview.Document = MyPrintDocument
 
    ' Make the text to print.
    AllParagraphs = New List(Of ParagraphInfo)
    AllParagraphs.Add(New ParagraphInfo(45, "28"))
    AllParagraphs.Add(New ParagraphInfo(36, "Printing"))
    ... Code to create other ParagraphInfo structures omitted...
End Sub

This code declares a PrintDocument object named MyPrintDocument. It uses the WithEvents keyword so it will be easy to catch the object’s events.

The code then declares lists to hold all of the ParagraphInfo structures that it will print and those that have not yet been printed.

When the program’s form loads, the code initializes these variables and adds a series of ParagraphInfo structures containing the text it will print to the AllParagraphs collection.

When the PrintDocument object starts drawing a printout, the BeginPrint event handler shown in the following code executes:

' Get ready to print pages.
Private Sub MyPrintDocument_BeginPrint() Handles MyPrintDocument.BeginPrint
    ' We have not yet printed any pages.
    PagesPrinted = 0
 
    ' Make a copy of the text to print.
    ParagraphsToPrint = New List(Of ParagraphInfo)
    For Each para_info As ParagraphInfo In AllParagraphs
        ParagraphsToPrint.Add(
            New ParagraphInfo(para_info.FontSize, para_info.Text))
    Next para_info
End Sub

This code resets the page number variable PagesPrinted. It then copies the ParagraphInfo structures from the AllParagraphs list (which holds all of the data) into the ParagraphsToPrint list (which holds those paragraphs that have not yet been printed).

After the BeginPrint event handler finishes, the PrintDocument object starts printing pages. Before it prints each page, the object raises its QueryPageSettings event. The program uses the following code to catch this event and prepare the next page for printing:

' Set the margins for the following page.
Private Sub MyPrintDocument_QueryPageSettings(
 sender As Object, e As QueryPageSettingsEventArgs) _
 Handles MyPrintDocument.QueryPageSettings
    ' Use a 1 inch gutter (printer units are 100 per inch).
    Const gutter As Integer = 100
 
    ' See if the next page will be the first, odd, or even.
    If PagesPrinted = 0 Then
        ' First page. Increase the left margin.
        e.PageSettings.Margins.Left += gutter
    ElseIf (PagesPrinted Mod 2) = 0 Then
        ' Odd page. Shift the margins right.
        e.PageSettings.Margins.Left += gutter
        e.PageSettings.Margins.Right -= gutter
    Else
        ' Even page. Shift the margins left.
        e.PageSettings.Margins.Left -= gutter
        e.PageSettings.Margins.Right += gutter
    End If
End Sub

This code determines whether the next page will be odd or even numbered and adjusts the page’s margin appropriately to create the gutter.

After each QueryPageSettings event, the PrintDocument object raises its PrintPage event to generate the corresponding page. The following code shows the most complicated part of the program, the PrintPage event handler:

' Print the next page.
Private Sub MyPrintDocument_PrintPage(sender As Object, e As PrintPageEventArgs) _
 Handles MyPrintDocument.PrintPage
    ' Increment the page number.
    PagesPrinted += 1
 
    ' Draw the margins (for debugging).
    'e.Graphics.DrawRectangle(Pens.Red, e.MarginBounds)
 
    ' Print the page number right justified 
    ' in the upper corner opposite the gutter
    ' and outside of the margin.
    Dim x As Integer
    Using string_format As New StringFormat
        ' See if this is an odd or even page.
        If (PagesPrinted Mod 2) = 0 Then
            ' This is an even page. 
            ' The gutter is on the right and
            ' the page number is on the left.
            x = (e.MarginBounds.Left + e.PageBounds.Left)  2
            string_format.Alignment = StringAlignment.Near
        Else
            ' This is an odd page.
            ' The gutter is on the left and
            ' the page number is on the right.
            x = (e.MarginBounds.Right + e.PageBounds.Right)  2
            string_format.Alignment = StringAlignment.Far
        End If
 
        ' Print the page number.
        Using the_font As New Font("Times New Roman", 20,
         FontStyle.Regular, GraphicsUnit.Point)
            e.Graphics.DrawString(PagesPrinted.ToString,
                the_font, Brushes.Black, x,
                (e.MarginBounds.Top + e.PageBounds.Top)  2,
                string_format)
        End Using ' the_font
 
        ' Draw the rest of the text left justified,
        ' wrap at words, and don't draw partial lines.
        string_format.Alignment = StringAlignment.Near
        string_format.FormatFlags = StringFormatFlags.LineLimit
        string_format.Trimming = StringTrimming.Word
 
        ' Draw some text.
        Dim paragraph_info As ParagraphInfo
        Dim ymin As Integer = e.MarginBounds.Top
        Dim layout_rect As RectangleF
        Dim text_size As SizeF
        Dim characters_fitted As Integer
        Dim lines_filled As Integer
        Do While ParagraphsToPrint.Count > 0
            ' Print the next paragraph.
            paragraph_info = ParagraphsToPrint(0)
            ParagraphsToPrint.RemoveAt(0)
 
            ' Get the area available for this paragraph.
            layout_rect = New RectangleF(
                e.MarginBounds.Left, ymin,
                e.MarginBounds.Width,
                e.MarginBounds.Bottom - ymin)
            ' Work around bug where MeasureString
            ' thinks characters fit if height <= 0.
            If layout_rect.Height < 1 Then layout_rect.Height = 1
 
            ' See how big the text will be and 
            ' how many characters will fit.
            ' Get the font.
            Using the_font As New Font("Times New Roman",
             paragraph_info.FontSize, FontStyle.Regular, GraphicsUnit.Point)
                text_size = e.Graphics.MeasureString(
                    paragraph_info.Text, the_font,
                    New SizeF(layout_rect.Width, layout_rect.Height),
                    string_format, characters_fitted, lines_filled)
 
                ' See if any characters will fit.
                If characters_fitted > 0 Then
                    ' Draw the text.
                    e.Graphics.DrawString(paragraph_info.Text,
                        the_font, Brushes.Black,
                        layout_rect, string_format)
 
                    ' Debugging: Draw a rectangle around the text.
                    'e.Graphics.DrawRectangle(Pens.Green,
                    '    layout_rect.Left,
                    '    layout_rect.Top,
                    '    text_size.Width,
                    '    text_size.Height)
 
                    ' Increase the location where we can start.
                    ' Add a little interparagraph spacing.
                    ymin += CInt(text_size.Height +
                        e.Graphics.MeasureString("M", the_font).Height / 2)
                End If
            End Using ' the_font
 
            ' See if some of the paragraph didn't fit on the page.
            If characters_fitted < Len(paragraph_info.Text) Then
                ' Some of the paragraph didn't fit.
                ' Prepare to print the rest on the next page.
                paragraph_info.Text = paragraph_info.Text.
                    Substring(characters_fitted)
                ParagraphsToPrint.Insert(0, paragraph_info)
 
                ' That's all that will fit on this page.
                Exit Do
            End If
        Loop
    End Using ' string_format 
 
    ' If we have more paragraphs, we have more pages.
    e.HasMorePages = (ParagraphsToPrint.Count > 0)
End Sub

The PrintPage event handler starts by incrementing the number of pages printed. It then includes commented code to draw a rectangle around the page’s margins. When you are debugging a printing routine, drawing this rectangle can help you see where your drawing is in relation to the page’s margins.

Next, the routine creates a font for the page number. Depending on whether this page is odd or even numbered, it calculates an X coordinate halfway between the non-gutter margin and the edge of the printable page. It sets a StringFormat object’s Alignment property to make numbers in the left margin left-justified and to make numbers in the right margin right-justified. It then draws the page number at the calculated X position, halfway between the top margin and the paper’s top printable boundary.

The program then prepares to draw the text for this page. It sets the StringFormat object’s properties so that the text is left-justified and lines wrap at word boundaries instead of in the middle of words.

It then sets the FormatFlags property to LineLimit. If only part of a line of text would fit vertically on the page, this makes Visual Basic not draw the line rather than draw just the top halves of its letters.

After this preparation, the program sets variable ymin to the minimum Y coordinate where the routine can draw text. Initially, this is the page’s top margin. It then enters a Do loop to process as much text as will fit on the page.

Inside the loop, the program takes the first ParagraphInfo structure from the ParagraphsToPrint list and makes a font that has the right size for that paragraph. It creates a RectangleF structure representing the remaining area on the page. This includes the area between the left and right margins horizontally, and between ymin and the bottom margin vertically.

The program then uses the e.Graphics object’s MeasureString method to see how much space the next piece of text will need. It passes MeasureString the layout rectangle’s size and the StringFormat object so Visual Basic can decide how it will need to wrap the paragraph’s text when it draws it. The code also passes in the variables characters_fitted and lines_filled. These parameters are passed by reference so MeasureString can fill in the number of characters and lines it could draw within the target rectangle.

The routine then checks characters_fitted to see if any characters will fit in the available area. If any characters can fit, the program draws the paragraph. Commented code draws a rectangle around the text to help with debugging. The program increases ymin by the paragraph’s printed height plus half of the font’s height to provide a break between paragraphs.

Next, the program determines whether the entire paragraph fits in the target rectangle. If some of the paragraph did not fit, the program stores the remaining text in the ParagraphInfo structure and puts the structure back at the beginning of the ParagraphsToPrint list so it can be printed on the next page. The code then exits the Do loop because the current page is full.

When the page is full or the ParagraphsToPrint list is empty, the PrintPage event handler is finished. The code sets e.HasMorePages to True if m_ParagraphsToPrint is not empty.

Finally, when the PrintDocument has finished printing the whole document, the following EndPrint event handler executes:

' Clean up.
Private Sub MyPrintDocument_EndPrint() Handles MyPrintDocument.EndPrint
    ParagraphsToPrint = Nothing
End Sub

The EndPrint event handler cleans up by setting the ParagraphsToPrint variable to Nothing, freeing up the list’s memory. In this program, freeing the list is a small matter. In a program that allocated more elaborate data structures, cleaning up in this event handler might be more important.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.107.169