home *** CD-ROM | disk | FTP | other *** search
/ Programming Tool Box / SIMS_2.iso / code / print / prtprev / genprint.txt < prev    next >
Text File  |  1994-04-03  |  19KB  |  539 lines

  1. How to Add Print Preview to Visual Basic Applications         [B_VBasic]
  2. ID: Q113236    CREATED: 29-MAR-1994   MODIFIED:            
  3. 2.00 3.00
  4. Windows
  5. ENDUSER | 
  6.  
  7. --------------------------------------------------------------
  8. The information in this article applies to:
  9.  
  10. - Standard and Professional Editions of Microsoft Visual Basic
  11.   Programming System for Windows, version 2.0 and 3.0
  12. --------------------------------------------------------------
  13.  
  14. SUMMARY
  15. =======
  16.  
  17. This article describes how to create printing routines that can
  18. print to the printer or to a picture box. This enables you to add
  19. print preview capabilities to your Visual Basic applications.
  20.  
  21. There are several ways that you could implement print preview in
  22. your applications. This article describes one method that is easy
  23. to do in Visual Basic and works well.
  24.  
  25. MORE INFORMATION
  26. ================
  27.  
  28. Generic Printing
  29. ----------------
  30.  
  31. It would be ideal to have a generic print routine that could print
  32. to the printer or to the screen depending on what you pass it. The
  33. Visual Basic printer object and picture box control have many of the
  34. same methods and properties. For example, both of these are valid:
  35.  
  36.    Printer.Print AString
  37.    Picture1.Print AString
  38.  
  39. It would be nice if you could pass a generic object to a subroutine
  40. and the subroutine would use the Print method off of the generic object
  41. as in this example:
  42.  
  43.    Call PrintJob(Printer)
  44.    Call PrintJob(Picture1)
  45.  
  46.    Sub PrintJob(GenericObject As Object)
  47.       GenericObject.Print AString
  48.    End Sub
  49.  
  50. Unfortunately, this is not possible. The Visual Basic Printer object
  51. is a system object, so it can't be passed as a parameter.
  52.  
  53. This leaves you with two choices in Visual Basic. You could create two
  54. routines -- one for printing to the printer and one for print preview.
  55.  However, the code would not be reusable in your future projects. The
  56. second approach is to write your own set of routines that can print to
  57. the printer or a picture box based on the value of a flag. This is the
  58. method used in the example code given below. Once you create the
  59. routines, you can re-use them in future programs.
  60.  
  61. The example creates routines that closely mimic Visual Basic's built in
  62. methods and properties. However, you could use this approach to create
  63. high-level routines that greatly simplify your printing needs.
  64.  
  65. The routines work by checking the variable PrinterFlag. PrinterFlag is
  66. True when printing is going to the printer and False when printing to
  67. the picture box.
  68.  
  69. Here's the print routine from the example. Notice how it is just a
  70. shell function that determines what to print to and then does it.
  71.  
  72.    Sub PrintPrint (PrintVar)
  73.       If PrinterFlag Then
  74.          Printer.Print PrintVar
  75.       Else
  76.          objPrint.Print PrintVar
  77.       End If
  78.    End Sub
  79.  
  80. With just a few simple routines like this, you can start to do
  81. generic printing.
  82.  
  83. Scaling
  84. -------
  85.  
  86. To accomplish print preview, the program must scale the output to the
  87. picture box to match the output on the printer.
  88.  
  89. In the example, the PrintStartDoc routine initializes the printer or
  90. picture box and sets up the scaling. The width and height of the paper
  91. are passed to the PrintStartDoc routine. These dimensions are used to
  92. determine the non-printable area of the printer object, find the ratio
  93. of the picture box to the printer, re-size the picture box, and scale
  94. the picture box. The picture box is scaled with the Scale method. After
  95. setting the scale of the picture box, graphic methods use the new
  96. coordinates. For an 8.5 x 11 inch piece of paper the picture box is
  97. scaled with this command:
  98.  
  99.    Picture1.Scale (0, 0)-(8.5, 11)
  100.  
  101. The Scale method does not scale fonts. To scale the fonts, use the
  102. ratio of the picture box height divided by the printer's height in
  103. inches. Then multiply by this ratio to determine the correct font
  104. size within the picture box. Here is the PrintFontSize routine that
  105. sets the appropriate font sizes in the example:
  106.  
  107.    Sub PrintFontSize (pSize)
  108.       If PrinterFlag Then
  109.          Printer.FontSize = pSize
  110.       Else
  111.          'Sized by ratio since Scale method does not effect FontSize
  112.          ObjPrint.FontSize = pSize * Ratio
  113.       End If
  114.    End Sub
  115.  
  116. The ratio used to calculate the font size can be applied to anything
  117. you need to scale in the picture box that is not automatically scaled
  118. by the Scale method. The ratio is also used in the PrintPicture routine
  119. to scale pictures.
  120.  
  121. Step-by-Step Example
  122. --------------------
  123.  
  124. 1. Start a new project in Visual Basic. Form1 is created by default.
  125.  
  126. 2. Add a command button (Command1), a check box (Check1), and two
  127.    picture boxes (Picture1 and Picture2) to the form.
  128.  
  129. 3. Put the following code in the command button click event:
  130.  
  131.    Sub Command1_Click ()
  132.    'Setup (Could be done at design time or in form load)
  133.    'Make printing stick
  134.    Picture1.AutoRedraw = True
  135.    'Add a palette for 256 colors
  136.    Picture1.Picture = LoadPicture("C:\VB\PASTEL.DIB")
  137.    'Setup hidden picture
  138.    Picture2.AutoRedraw = False
  139.    Picture2.ScaleMode = 3 'Pixels
  140.    Picture2.Visible = False
  141.    Picture2.AutoSize = True
  142.    Picture2.Picture = LoadPicture("C:\VB\METAFILE\BUSINESS\PRINTER.WMF")
  143.  
  144.    'This print job can go to the printer or the picture box
  145.    If Check1.Value = 0 Then PrinterFlag = True
  146.    PrintStartDoc Picture1, PrinterFlag, 8.5, 11
  147.  
  148.    'All the subs use inches
  149.    PrintBox 1, 1, 6.5, 9
  150.    PrintLine 1.1, 2, 7.4, 2
  151.    PrintPicture Picture2, 1.1, 1.1, .8, .8
  152.    PrintFilledBox 2.1, 1.2, 5.2, .7, RGB(200, 200, 200)
  153.    PrintFontName "Arial"
  154.    PrintCurrentX 2.3
  155.    PrintCurrentY 1.3
  156.    PrintFontSize 35
  157.    PrintPrint "Visual Basic Printing"
  158.    For x = 3 To 5.5 Step .2
  159.       PrintCircle x, 3.5, .75
  160.    Next
  161.    PrintFontName "Courier New"
  162.    PrintFontSize 30
  163.    PrintCurrentX 1.5
  164.    PrintCurrentY 5
  165.    PrintPrint "It is possible to do"
  166.    PrintFontSize 24
  167.    PrintCurrentX 1.5
  168.    PrintCurrentY 6.5
  169.    PrintPrint "It is possible to do print"
  170.    PrintFontSize 18
  171.    PrintCurrentX 1.5
  172.    PrintCurrentY 8
  173.    PrintPrint "It is possible to do print preview"
  174.    PrintFontSize 12
  175.    PrintCurrentX 1.5
  176.    PrintCurrentY 9.5
  177.    PrintPrint "It is possible to do print preview with good results."
  178.    PrintEndDoc
  179.    End Sub
  180.  
  181. 4. Add a new Module to the project (MODULE1.BAS).
  182.  
  183. 5. Put the following code in the basic module:
  184.  
  185.    Option Explicit
  186.  
  187.    ' The following Types, Declares, and Constants are only necessary for the
  188.  
  189.    ' PrintPicture routine
  190.    '=========================================================================
  191. =
  192.    Type BITMAPINFOHEADER_TYPE
  193.       biSize As Long
  194.       biWidth As Long
  195.       biHeight As Long
  196.       biPlanes As Integer
  197.       biBitCount As Integer
  198.       biCompression As Long
  199.       biSizeImage As Long
  200.       biXPelsPerMeter As Long
  201.       biYPelsPerMeter As Long
  202.       biClrUsed As Long
  203.       biClrImportant As Long
  204.       bmiColors As String * 1024
  205.    End Type
  206.  
  207.    Type BITMAPINFO_TYPE
  208.       BitmapInfoHeader As BITMAPINFOHEADER_TYPE
  209.       bmiColors As String * 1024
  210.    End Type
  211.  
  212.    'Each of the following declares should be entered on a single line
  213.    Declare Function GetDIBits Lib "gdi" (ByVal hDC As Integer,
  214.       ByVal hBitmap As Integer, ByVal nStartScan As Integer,
  215.       ByVal nNumScans As Integer, ByVal lpBits As Long,
  216.       BitmapInfo As BITMAPINFO_TYPE, ByVal wUsage As Integer) As Integer
  217.    Declare Function StretchDIBits Lib "gdi" (ByVal hDC As Integer,
  218.       ByVal DestX As Integer, ByVal DestY As Integer,
  219.       ByVal wDestWidth As Integer, ByVal wDestHeight As Integer,
  220.       ByVal SrcX As Integer, ByVal SrcY As Integer,
  221.       ByVal wSrcWidth As Integer, ByVal wSrcHeight As Integer,
  222.       ByVal lpBits As Long, BitsInfo As BITMAPINFO_TYPE,
  223.       ByVal wUsage As Integer, ByVal dwRop As Long) As Integer
  224.    Declare Function GlobalAlloc Lib "kernel" (ByVal wFlags As Integer,
  225.       ByVal lMem As Long) As Integer
  226.    Declare Function GlobalLock Lib "kernel" (ByVal hMem As Integer) As Long
  227.    Declare Function GlobalUnlock Lib "kernel"
  228.       (ByVal hMem As Integer) As Integer
  229.    Declare Function GlobalFree Lib "kernel" (ByVal hMem As Integer) As Intege
  230. r
  231.  
  232.    Global Const SRCCOPY = &HCC0020
  233.    Global Const BI_RGB = 0
  234.    Global Const DIB_RGB_COLORS = 0
  235.    Global Const GMEM_MOVEABLE = 2
  236.  
  237.    'Module level variables set in PrintStartDoc
  238.    'Flag indicating Printing or Previewing
  239.    Dim PrinterFlag
  240.    'Object used for Print Preview
  241.    Dim ObjPrint As Control
  242.    'Storage for output objects original scale mode
  243.    Dim sm
  244.    'The size ratio between the actual page and the print preview object
  245.    Dim Ratio
  246.    'Size of the non-printable area on printer
  247.    Dim LRGap
  248.    Dim TBGap
  249.    'The actual paper size (8.5 x 11 normally)
  250.    Dim PgWidth
  251.    Dim PgHeight
  252.  
  253.    Sub PrintStartDoc (objToPrintOn As Control, PF, PaperWidth, PaperHeight)
  254.       Dim psm
  255.       Dim fsm
  256.       Dim HeightRatio
  257.       Dim WidthRatio
  258.  
  259.       'Set the flag that determines whether printing or previewing
  260.       PrinterFlag = PF
  261.  
  262.       'Set the physical page size
  263.       PgWidth = PaperWidth
  264.       PgHeight = PaperHeight
  265.  
  266.       'Find the size of the non-printable area on the printer
  267.       'Will be used to offset coordinates
  268.       'These formulas assume the non-printable area is centered on the page
  269.       psm = Printer.ScaleMode
  270.       Printer.ScaleMode = 5 'Inches
  271.       LRGap = (PgWidth - Printer.ScaleWidth) / 2
  272.       TBGap = (PgHeight - Printer.ScaleHeight) / 2
  273.       Printer.ScaleMode = psm
  274.  
  275.       'Initialize printer or preview object
  276.       If PrinterFlag Then
  277.          sm = Printer.ScaleMode
  278.          Printer.ScaleMode = 5 'Inches
  279.          Printer.Print "";
  280.       Else
  281.          'Set the object used for preview
  282.          Set ObjPrint = objToPrintOn
  283.          'Scale Object to Printer's printable area in Inches
  284.          sm = ObjPrint.ScaleMode
  285.          ObjPrint.ScaleMode = 5 'Inches
  286.          'Compare the height and with ratios to determine the
  287.          'Ratio to use and how to size the picture box
  288.          HeightRatio = ObjPrint.ScaleHeight / PgHeight
  289.          WidthRatio = ObjPrint.ScaleWidth / PgWidth
  290.          If HeightRatio < WidthRatio Then
  291.             Ratio = HeightRatio
  292.             'Re-size picture box - this does not work on a form
  293.             fsm = ObjPrint.Parent.ScaleMode
  294.             ObjPrint.Parent.ScaleMode = 5 'Inches
  295.             ObjPrint.Width = PgWidth * Ratio
  296.             ObjPrint.Parent.ScaleMode = fsm
  297.          Else
  298.             Ratio = WidthRatio
  299.             'Re-size picture box - this does not work on a form
  300.             fsm = ObjPrint.Parent.ScaleMode
  301.             ObjPrint.Parent.ScaleMode = 5 'Inches
  302.             ObjPrint.Height = PgHeight * Ratio
  303.             ObjPrint.Parent.ScaleMode = fsm
  304.          End If
  305.          'Set default properties of picture box to match printer
  306.          'There are many that you could add here
  307.          ObjPrint.Scale (0, 0)-(PgWidth, PgHeight)
  308.          ObjPrint.FontName = Printer.FontName
  309.          ObjPrint.FontSize = Printer.FontSize * Ratio
  310.          ObjPrint.ForeColor = Printer.ForeColor
  311.          ObjPrint.Cls
  312.       End If
  313.    End Sub
  314.  
  315.    Sub PrintCurrentX (XVal)
  316.       If PrinterFlag Then
  317.          Printer.CurrentX = XVal - LRGap
  318.       Else
  319.          ObjPrint.CurrentX = XVal
  320.       End If
  321.    End Sub
  322.  
  323.    Sub PrintCurrentY (YVal)
  324.       If PrinterFlag Then
  325.          Printer.CurrentY = YVal - TBGap
  326.       Else
  327.          ObjPrint.CurrentY = YVal
  328.       End If
  329.    End Sub
  330.  
  331.    Sub PrintFontName (pFontName)
  332.       If PrinterFlag Then
  333.          Printer.FontName = pFontName
  334.       Else
  335.          ObjPrint.FontName = pFontName
  336.       End If
  337.    End Sub
  338.  
  339.    Sub PrintFontSize (pSize)
  340.       If PrinterFlag Then
  341.          Printer.FontSize = pSize
  342.       Else
  343.          'Sized by ratio since Scale method does not effect FontSize
  344.          ObjPrint.FontSize = pSize * Ratio
  345.       End If
  346.    End Sub
  347.  
  348.    Sub PrintPrint (PrintVar)
  349.       If PrinterFlag Then
  350.          Printer.Print PrintVar
  351.       Else
  352.          ObjPrint.Print PrintVar
  353.       End If
  354.    End Sub
  355.  
  356.    Sub PrintLine (bLeft0, bTop0, bLeft1, bTop1)
  357.       If PrinterFlag Then
  358.          'The following should be entered on a single line
  359.          Printer.Line (bLeft0 - LRGap, bTop0 - TBGap)-
  360.             (bLeft1 - LRGap, bTop1 - TBGap)
  361.       Else
  362.          ObjPrint.Line (bLeft0, bTop0)-(bLeft1, bTop1)
  363.       End If
  364.    End Sub
  365.  
  366.    Sub PrintBox (bLeft, bTop, bWidth, bHeight)
  367.       If PrinterFlag Then
  368.          'The following should be entered on a single line
  369.          Printer.Line (bLeft - LRGap, bTop - TBGap)-
  370.             (bLeft + bWidth - LRGap, bTop + bHeight - TBGap), , B
  371.       Else
  372.          ObjPrint.Line (bLeft, bTop)-(bLeft + bWidth, bTop + bHeight), , B
  373.       End If
  374.    End Sub
  375.  
  376.    Sub PrintFilledBox (bLeft, bTop, bWidth, bHeight, color)
  377.       If PrinterFlag Then
  378.          'The following should be entered on a single line
  379.          Printer.Line (bLeft - LRGap, bTop - TBGap)-
  380.             (bLeft + bWidth - LRGap, bTop + bHeight - TBGap), color, BF
  381.       Else
  382.          'The following should be entered on a single line
  383.          ObjPrint.Line (bLeft, bTop)-(bLeft + bWidth, bTop + bHeight),
  384.             color, BF
  385.       End If
  386.    End Sub
  387.  
  388.    Sub PrintCircle (bLeft, bTop, bRadius)
  389.       If PrinterFlag Then
  390.          Printer.Circle (bLeft - LRGap, bTop - TBGap), bRadius
  391.       Else
  392.          ObjPrint.Circle (bLeft, bTop), bRadius
  393.       End If
  394.    End Sub
  395.  
  396.    Sub PrintNewPage ()
  397.       If PrinterFlag Then
  398.          Printer.NewPage
  399.       Else
  400.          ObjPrint.Cls
  401.       End If
  402.    End Sub
  403.  
  404.    'The following should be entered on a single line
  405.    Sub PrintPicture (picSource As Control, ByVal pLeft, ByVal pTop,
  406.       ByVal pWidth, ByVal pHeight)
  407.       'Picture Box should have autoredraw = False, ScaleMode = Pixel
  408.       ' Also can have visible=false, Autosize = true
  409.  
  410.       Dim BitmapInfo As BITMAPINFO_TYPE
  411.       Dim DesthDC As Integer
  412.       Dim hMem As Integer
  413.       Dim lpBits As Long
  414.       Dim r As Integer
  415.  
  416.       'Precaution
  417.       If pLeft < LRGap Or pTop < TBGap Then Exit Sub
  418.       If pWidth < 0 Or pHeight < 0 Then Exit Sub
  419.       If pWidth + pLeft > PgWidth - LRGap Then Exit Sub
  420.       If pHeight + pTop > PgHeight - TBGap Then Exit Sub
  421.       picSource.ScaleMode = 3 'Pixels
  422.       picSource.AutoRedraw = False
  423.       picSource.Visible = False
  424.       picSource.AutoSize = True
  425.  
  426.       If PrinterFlag Then
  427.          Printer.ScaleMode = 3 'Pixels
  428.          'Calculate size in pixels
  429.          pLeft = ((pLeft - LRGap) * 1440) / Printer.TwipsPerPixelX
  430.          pTop = ((pTop - TBGap) * 1440) / Printer.TwipsPerPixelY
  431.          pWidth = (pWidth * 1440) / Printer.TwipsPerPixelX
  432.          pHeight = (pHeight * 1440) / Printer.TwipsPerPixelY
  433.          Printer.Print "";
  434.          DesthDC = Printer.hDC
  435.       Else
  436.          ObjPrint.Scale
  437.          ObjPrint.ScaleMode = 3 'Pixels
  438.          'Calculate size in pixels
  439.          pLeft = ((pLeft * 1440) / Screen.TwipsPerPixelX) * Ratio
  440.          pTop = ((pTop * 1440) / Screen.TwipsPerPixelY) * Ratio
  441.          pWidth = ((pWidth * 1440) / Screen.TwipsPerPixelX) * Ratio
  442.          pHeight = ((pHeight * 1440) / Screen.TwipsPerPixelY) * Ratio
  443.          DesthDC = ObjPrint.hDC
  444.       End If
  445.  
  446.       BitmapInfo.BitmapInfoHeader.biSize = 40
  447.       BitmapInfo.BitmapInfoHeader.biWidth = picSource.ScaleWidth
  448.       BitmapInfo.BitmapInfoHeader.biHeight = picSource.ScaleHeight
  449.       BitmapInfo.BitmapInfoHeader.biPlanes = 1
  450.       BitmapInfo.BitmapInfoHeader.biBitCount = 8
  451.       BitmapInfo.BitmapInfoHeader.biCompression = BI_RGB
  452.  
  453.       'Enter the following on a single line
  454.       hMem = GlobalAlloc(GMEM_MOVEABLE, (CLng(picSource.ScaleWidth + 3) \ 4)
  455. *
  456.          4 * picSource.ScaleHeight)'DWORD ALIGNED
  457.       lpBits = GlobalLock(hMem)
  458.  
  459.       'Enter the following on a single line
  460.       r = GetDIBits(picSource.hDC, picSource.Image, 0, picSource.ScaleHeight,
  461.  
  462.          lpBits, BitmapInfo, DIB_RGB_COLORS)
  463.       If r <> 0 Then
  464.          'Enter the following on a single line
  465.          r = StretchDIBits(DesthDC, pLeft, pTop, pWidth, pHeight, 0, 0,
  466.             picSource.ScaleWidth, picSource.ScaleHeight, lpBits, BitmapInfo,
  467.  
  468.             DIB_RGB_COLORS, SRCCOPY)
  469.       End If
  470.  
  471.       r = GlobalUnlock(hMem)
  472.       r = GlobalFree(hMem)
  473.  
  474.       If PrinterFlag Then
  475.          Printer.ScaleMode = 5 'Inches
  476.       Else
  477.          ObjPrint.ScaleMode = 5'Inches
  478.          ObjPrint.Scale (0, 0)-(PgWidth, PgHeight)
  479.       End If
  480.    End Sub
  481.  
  482.    Sub PrintEndDoc ()
  483.       If PrinterFlag Then
  484.          Printer.EndDoc
  485.          Printer.ScaleMode = sm
  486.       Else
  487.          ObjPrint.ScaleMode = sm
  488.       End If
  489.    End Sub
  490.  
  491. 6. Save the project.
  492.  
  493. 7. Run it.
  494.  
  495. Click the command button with the check box checked to preview the
  496. page. Click the command button with the check box cleared to print
  497. the page.
  498.  
  499. Notes
  500. -----
  501.  
  502.  - The accuracy of the preview really depends on the fonts available.
  503.    This method relies upon Windows to return the most appropriate font
  504.    and size. You could come up with your own algorythm for choosing a
  505.    font size. The TextWidth and TextHeight methods of the Printer
  506.    object and picture box may be useful for this.
  507.  
  508.  - The example uses inches the device independent unit of measurement.
  509.    But you could use twips, points, millimeters, or centemeters.
  510.  
  511.  - You may want to implement the preview window in a scrollable viewport.
  512.    For more information, please see the following article in the Microsoft
  513.    Knowledge Base:
  514.  
  515.    ARTICLE-ID: Q71068
  516.    TITLE     : How to Create Scrollable Viewports in Visual Basic
  517.  
  518.  - There are other methods that you could use to preview printing. You
  519.    could pass a device context to a routine that uses only Windows API
  520.    functions to draw and print. You could also create a picture that you
  521.    either stretch to the printer or to the screen.
  522.  
  523. Additional reference words: 2.00 3.00
  524. KBCategory: APrg
  525. KBSubcategory: APrgPrint
  526. \*
  527. \* MSINTERNAL:
  528. \* Tested with: HP Laserjet II
  529. \*              HP LaserJet III
  530. \*              HP LaserJet IIIsi
  531. \*              HP LaserJet IIIsi PostScript
  532. \*              Compaq QVision 1024*768*256 Lg. Res
  533. \*              ATI Graphics Ultra 800*600*256
  534. \*              VGA
  535. \* Tech Review: LyleH
  536. ================================================================================
  537. Created_by: TIMMCB     Edit_review:           Edited:            
  538. Modified_by:           Tech_review:           Reviewed:            
  539.