DevCity.NET -
GDI+ Chart Success Part 7: Printing Charts
Ged Mead

Ged Mead (XTab) is a Microsoft Visual Basic MVP who has been working on computer software and design for more than 25 years. His journey has taken him through many different facets of IT. These include training as a Systems Analyst, working in a mainframe software development environment, creating financial management systems and a short time spent on military laptop systems in the days when it took two strong men to carry a 'mobile' system.

Based in an idyllic lochside location in the West of Scotland, he is currently involved in an ever-widening range of VB.NET, WPF and Silverlight development projects. Now working in a consultancy environment, his passion however still remains helping students and professional developers to take advantage of the ever increasing range of sophisticated tools available to them.

Ged is a regular contributor to forums on vbCity and authors articles for DevCity. He is a moderator on VBCity and the MSDN Tech Forums and spends a lot of time answering technical questions there and in several other VB forum sites. Senior Editor for DevCity.NET, vbCity Developer Community Leader and Admin, and DevCity.NET Newsletter Editor. He has written and continues to tutor a number of free online courses for VB.NET developers.

by Ged Mead
Published on 10/16/2006

   In previous articles several variations of simple charts were created.   These could be viewed on screen but at some time or other you may need to print out hard copies.

   This article shows you how.

Pie Chart


  In the first article of the series we created a very basic pie chart using the GDI+ methods available to us in the .NET Framework.   So for the first example in this article we will recreate this chart and then print it.

  The Pie Chart in that first article is drawn directly on to the surface of the Windows Form.  This highlights a useful and interesting fact relating to how drawing and printing works: you can often use exactly the same code to draw (i.e. print) to a printer as you use to draw on to a form.

   As it happens, in our first example this will result in unnecessary duplication of code.  This  is because in Part 1 of the series we didn't put the drawing code in a procedure that we could call multiple times from various parts of the application.  We put it all in the OnPaint event of the form.

   At the price of writing ugly code I'm going to stick with this approach because it will make it much easier to compare directly what we did in Part 1 to draw the chart with what we want to do here to print out the result.   In other words, I don't want to fall into the trap of tidying up the original code just to make it look better when the original idea was to write code that a beginner could understand and use.

   So here is the code we used originally.   You can copy and paste it into a new form in a project of your own or check out the demo solution at the end of the article.   It consisted of the setting up of variables....

Structure GraphData
  Dim Amount As Single
  Dim Clr As Color
  Dim Description As String

' Create a constructor for the Structure
  Sub New(ByVal amt As Integer, ByVal col As Color, ByVal desc As String)
   Me.Amount = amt
   Me.Clr = col
   Me.Description = desc
  End Sub
End Structure

Dim Companies As New System.Collections.ArrayList

Creating some dummy data for display purposes....

Private Sub frmPrintPie_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
  Companies.Add(New GraphData(50, Color.Blue, "Muir Inc."))
  Companies.Add(New GraphData(75, Color.Yellow, "Philmas Co."))
  Companies.Add(New GraphData(62, Color.Red, "Xamco"))
  Companies.Add(New GraphData(27, Color.LightGreen, "Wright plc"))
End Sub

And this block of code in the form's OnPaint event to draw the pie chart and the key:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
  Dim g As Graphics = e.Graphics
  g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
  Dim rect As Rectangle = New Rectangle(100, 105, 150, 150)
  Dim TotalCount As Single
   For Each gd As GraphData In Companies
    TotalCount += gd.Amount
' Create variables to hold the changing values of Angles
  Dim StartAngle As Single = 0
  Dim SweepAngle As Single = 0
   For Each gd As GraphData In Companies
    SweepAngle = 360 * gd.amount / TotalCount
New SolidBrush(gd.Clr), rect, StartAngle, SweepAngle) 

    g.DrawPie(New Pen(Color.Brown), rect, StartAngle, SweepAngle) 
    StartAngle += SweepAngle

' Create a Brush to draw the text
 Dim TextBrsh As Brush = New SolidBrush(Color.Black)
' Create a Font object instance for text display
 Dim TextFont As New Font("Arial", 12, FontStyle.Bold)
 g.DrawString("Chart Key", TextFont, TextBrsh, 310, 100)
 Dim pxFromTop As Integer = 135
  For Each gd As GraphData In Companies
   ' Draw bullet
   New SolidBrush(gd.Clr), 310, pxFromTop, 15, 15)
  ' Draw line round bullet.
   g.DrawEllipse(New Pen(Color.Black), 310, pxFromTop, 15, 15)
  ' Draw the text - color coded
  g.DrawString(gd.Description & " (" & gd.Amount & ")", _
   TextFont, TextBrsh, 360, pxFromTop)
  ' Increase gap from Top for next line
  pxFromTop += 30
End Sub 

   If you want a complete breakdown of how the above code works, you will find it explained step by step in the Part 1 article.


   Build the project and run it.  On the form you will see:


   In order to print what the user sees we will use a PrintDocument control.   While many people find some of the printing processes in VB.NET hard to fathom, this control at least is easy to understand and use.  Most times you will only want to use its Print method in order to fire the PrintPage event.  

    It sometimes takes a bit of a mindset shift to move ourselves away from thinking of "printing" being some kind of mechanical device that magically happens when you hit a print button.  If you stop and think about it for a moment it makes absolute sense to realise that all that really happens when the printer prints is that a series of dots are transferred to the printer paper.  We recognise those patterns as words and images; and this is exactly the same logic that we are perfectly happy to accept when we write code to draw strings, shapes or images on a form.  It really is literally a "Back to Basics" kind of approach.

   As I explained above, all we are going to do for this first pass at printing is to copy and paste the OnPaint code and use it again.    The first thing you need to do is to add a PrintDocument control to the form.  As you'll see when you drag it on to the form, it will automatically drop into the component tray below the actual form area itself; this being because it's a non-visual control.   You can leave the control's name as the default of PrintDocument1.

   Now go into the code window and select the PrintDocument1 from the left hand combobox and the PrintPage event from the combo on the right.    Copy and paste the OnPaint code above into this procedure.   The exact same drawing code will now be actioned as soon as the PrintPage event is fired.

   Therefore that just leaves us with the task of firing that PrintPage event.   Drag a button on to the form and in the button's click event add this code:


   Save the project, make sure your printer is turned on, run the project and click the print button that you just created.   All other things being equal, you should be rewarded with a hard copy printed version of the pie chart and its key.


Bar Chart - Drawing


   In Part 2 and Part 4 of this series we created bar charts using two different approaches.  For the purposes of this article on Printing I will simply copy and paste the code we used in the second of those articles and then print out the resulting image.   As both versions use a PictureBox control to house the chart there is in fact very little difference between them anyway.

Creating the Chart

   As I did on the previous page, I will just lump below all the code you need to draw the bar chart.  A step by step explanation of how it all works can be found beginning here in the original article.  

   Add a new form to your project, drag a PictureBox control on to it and name it PBBarChart.   Copy and paste in the following code to set up the variables and create some demonstration data.

Option Strict On
Imports System.Drawing.Drawing2D
Imports System.Collections 

Structure GraphData
 Dim Country As String
 Dim Sales As Integer
 Dim BarColor As Color

 Sub New(ByVal country As String, ByVal sales As Short, ByVal barcol As Color)
   Me.Country = country
   Me.Sales = sales
   Me.BarColor = barcol
 End Sub
End Structure

' Create Variables
Dim LeftMargin As Integer = 35
Dim RightMargin As Integer = 15
Dim BaseMargin As Integer = 35
Dim TopMargin As Integer = 10
Dim BarGap As Integer = 12
Dim SalesData As New ArrayList
Dim HighSale As Double ' Maximum sales figure
Dim VertScale As Double ' Scaling used for bar heights
Dim g As Graphics
Dim bmap As Bitmap
Dim VertLineLength As Integer
Dim BarWidth As Integer ' width of bars
Dim BaseLineLength As Integer ' X Axis length
Private Sub GetData()

SalesData.Add(New GraphData("Belgium", 934, Color.Blue))
SalesData.Add(New GraphData("Greece", 385, Color.DarkOrange))
SalesData.Add(New GraphData("Portugal", 1029, Color.Green))
SalesData.Add(New GraphData("Spain", 729, Color.IndianRed))
SalesData.Add(New GraphData("Turkey", 1472, Color.Tomato))
SalesData.Add(New GraphData("UK", 1142, Color.Aquamarine))
End Sub

  Now, the procedures used to draw the bar chart:

Private Function GetGraphics() As Graphics
 ' Make bmap the same size and resolution as the PictureBox
 bmap = New Bitmap(PBBarChart.Width, PBBarChart.Height, PBBarChart.CreateGraphics)
 ' Assign the Bitmap object to the Graphics object and Return it
 Return Graphics.FromImage(bmap)
End Function

Private Sub DrawVerticalAxis(ByVal g As Graphics)
  Dim StartPoint As New Point(LeftMargin, PBBarChart.Height - BaseMargin)
  Dim EndPoint As New Point(LeftMargin, TopMargin)
  Dim LinePen As New Pen(Color.Black, 2)
  g.DrawLine(LinePen, StartPoint, EndPoint)

  VertLineLength = PBBarChart.Height - (BaseMargin + TopMargin)

    For Each gd As GraphData In SalesData
     If gd.Sales > HighSale Then HighSale = gd.Sales

  Dim NextCent As Integer = CInt(HighSale)
  Do While NextCent Mod 100 <> 0
   NextCent += 1

 Dim TotalTicks As Integer = CInt(NextCent / 100)
 Dim YPos As Integer = CInt(VertLineLength / TotalTicks)
 Dim TickSP As New Point(LeftMargin - 5, StartPoint.Y - YPos)
 Dim TickEP As New Point(LeftMargin, StartPoint.Y - YPos)
 Dim ValueFont As New Font("Arial", 8, FontStyle.Regular)
  For i As Integer = 1 To TotalTicks
   g.DrawLine(New Pen(Color.Black), TickSP, TickEP) ' Tick mark
   CStr(i * 100), ValueFont, Brushes.Black, 2, TickSP.Y - 5)
   TickSP.Y = CInt(TickSP.Y - (VertLineLength / TotalTicks))
   TickEP.Y -= CInt(VertLineLength / TotalTicks)
End Sub

Draw the bars:

Private Sub Draw3DBars()
 g.DrawLine(New Pen(Color.Black), LeftMargin, PBBarChart.Height - BaseMargin, _
   PBBarChart.Width - RightMargin, PBBarChart.Height - BaseMargin)
 BaseLineLength = PBBarChart.Width - (LeftMargin + RightMargin)
 BarWidth = CInt((BaseLineLength / SalesData.Count) - BarGap)
 Dim BarStartX As Integer = LeftMargin + BarGap
 Dim BaseLine As Integer = PBBarChart.Height - BaseMargin
 VertScale = VertLineLength / HighSale
  For Each gd As GraphData In SalesData
   Dim Corners(3) As Point
   Corners(0) = New Point(BarStartX, BaseLine - 10)
   Corners(1) = New Point(BarStartX + BarWidth - 5, BaseLine - 10)
   Corners(2) = New Point(BarStartX + BarWidth, BaseLine)
   Corners(3) = New Point(BarStartX + 5, BaseLine)
   Dim BarHeight As Integer = CInt(gd.Sales * VertScale)

  Dim barmainBrush As New HatchBrush(HatchStyle.Percent50, gd.BarColor)
  Dim bartopBrush As New SolidBrush(gd.BarColor)
  ' Draw one complete bar
   For i As Integer = 0 To BarHeight - 11
    g.FillPolygon(barmainBrush, Corners)
Corners(0).Y -= 1
    Corners(1).Y -= 1
    Corners(2).Y -= 1
    Corners(3).Y -= 1
  g.FillPolygon(bartopBrush, Corners)
 ' Move the startpoint for the next bar
  BarStartX += CInt(BarWidth + BarGap)
 ' Dispose of brushes
End Sub 

Finally, the procedure that adds the legend:

Private Sub WriteTheNames()
   Dim TextStartX As Integer = LeftMargin + BarGap + 5
   ' Create a Brush to draw the text
   Dim TextBrsh As Brush = New SolidBrush(Color.Black)
   ' Create a Font object instance for text display
   ' dynamically adjusted font size would be useful:
   Dim fntSize As Integer = CInt(BarWidth / 7)
   Dim TextFont As New Font("Verdana", fntSize, FontStyle.Bold)
   ' Write them:
    For Each gd As GraphData In SalesData
     g.DrawString(gd.Country, TextFont, TextBrsh, TextStartX,
     CInt(PBBarChart.Height - (BaseMargin - 4)))
     TextStartX += CInt(BarWidth + BarGap)
End Sub



Bar Chart - Printing

The Easy Way and the Hard Way

   If I'd created the code for Part 4 knowing that I would eventually want to use it in more than one way I certainly would have set about it differently.   Certainly, for a start I would have avoided hard coding in the name of the Picturebox as I did in some of the procedures.  It wouldn't take a huge amount of effort to comb through the original code and replace hard coded names with parameters.  A couple more tweaks here and there and it would then be possible to use the PrintPage event of a PrintDocument control to draw the chart straight on to the printed page, much as we did with the pie chart.

   That however is still a harder hill than we need to climb because there is a much easier way we can go.   Because we have drawn the chart onto a PictureBox control we automatically have access to the PictureBox's Image property.   And of course that image will be the exact chart that we want to print out!  The GDI+ methods include one that draws an image (DrawImage - what else?) and we can draw an image to a printer just as easily as we can draw one to a screen - as we shall now see.

   First, drag a PrintDocument control from the Toolbox on to the form you are using to create the Bar Chart, then read on:


  Essentially, all we have to do is grab the image from the PictureBox, read the size of the PictureBox and pass this information to the DrawImage method.   By placing the DrawImage method in the PrintPage event of the PrintDocument control, we force the chart (i.e. the image) to be drawn to the printer.

   Here is the code for the PrintPage event:

Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
 ' Grab the image
 Dim ImageToPrint As Image = Me.PBBarChart.Image
 ' Set the X and Y position values that you want as the start point
 ' for printing on the page, together with the width and height of the image.

 Dim R As New Rectangle(20, 20, ImageToPrint.Width, ImageToPrint.Height)
 ' Draw the image (placing it within the rectangle defined above)
 e.Graphics.DrawImage(ImageToPrint, R)
End Sub

   I don't think there is anything I need to add to the commenting for you to see how it works.

    And as we did with the Pie Chart, you can add a second button to the form, name it BtnPrint and use it to call the PrintDocument's Print method:

Private Sub BtnPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnPrint.Click
End Sub

  The form will look something like this and you will be able to print out the displayed bar chart by clicking on the Print Graph button.



Line Chart - Drawing


    Parts 5 and 6 of this series contain detailed descriptions of how you can create and display various line charts.   For the printing demonstration I thought I would just stick to the final version covered in Part 6, that is the so called dynamic line chart.    This is mainly because the procedure printing the line chart from Part 5 is the same as that we just covered for the bar chart, i.e. grab the PictureBox image and print it.

   The chart in Part 6 was slightly more complex because it used two PictureBox controls side by side, one to display the vertical axis details and the other to display the changing chart display.  We would therefore need a way of printing both pictureboxes side by side if we want to replicate what the user sees on screen and put it on paper.  So that's what we are going to do.

   The code that enables a user to create the line chart is repeated below.  For full details of how it works, plus info on which controls will need to be placed on the form you will need to visit the Part 6 article here.  

' Variables to store the changing values to be charted (previous and current)
Private OldValue As Single = 0
Private NewValue As Single = 0
Dim XMove As Integer = 4
Dim Chunks As Integer = 12

Private Sub More_Generic_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
  ' Paint Guidelines on picGraph
 picGraph.Image = DisplayGuidelines(picGraph, Chunks)
  ' Paint Guidelines and Numbers on picValues
 picValues.Image = Me.DisplayVerticalValues(picValues, Chunks, SBUserValue.Minimum, SBUserValue.Maximum)
End Sub


Private Function DisplayGuidelines(ByVal PicBox As PictureBox, ByVal chunks As Integer) As Bitmap
' Step 1
' Create a bitmap to draw on and grab its Graphics Object

Dim bm As New Bitmap(PicBox.Width, PicBox.Height)
Dim gr As Graphics = Graphics.FromImage(bm)
' Step 2
' Draw guidelines on main chart.
' Get the total height available and split it into chunks

Dim total As Integer = PicBox.Height
Dim chunk As Single = total / chunks
' Step 3
For i As Single = chunk To total Step chunk
  gr.DrawLine(Pens.Green, 0, i, PicBox.Width, i)
Next i
' Step 4
' return the results.

Return bm
' Step 5
End Function
Private Function DisplayVerticalValues(ByVal PB As PictureBox, ByVal HowManyChunks As Single, _
ByVal MinValue As Single, ByVal MaxValue As Single) As Bitmap
' Step 1
Dim bmp As New Bitmap(PB.Width, PB.Height)
Dim gv As Graphics = Graphics.FromImage(bmp)
' Step 2
' Draw guidelines on values strip
' Get the total height available and split it into chunks
' This value represents a number of pixels

Dim TotalPixels As Integer = PB.Height
Dim SingleChunk As Single = TotalPixels / HowManyChunks
 For i As Single = SingleChunk To TotalPixels Step SingleChunk
   gv.DrawLine(Pens.Green, 0, i, PB.Width, i)
 Next i
' Step 3
' Draw Numbers as Text, correctly spaced vertically
' Begin with the highest value allowed

Dim NextMarker As Integer = MaxValue
' Calculate the plottable range
Dim ValueRange As Integer = MaxValue - MinValue
' Draw the numbers, decrementing values proportionately each time through the loop
 For i As Single = 0 To TotalPixels Step SingleChunk
   CStr(NextMarker), New Font("Verdana", 8, FontStyle.Regular), Brushes.Black, 1, i)
   NextMarker -= (ValueRange / HowManyChunks)
' Step 4
Return bmp
' Step 5
End Function
Private Function DisplayGuidelinesAndChart(ByVal PicBox As PictureBox, ByVal chunks As Integer, _
ByVal XMove As Integer, ByVal NewValue As Single, ByVal Min As Single, ByVal Max As Single) As Bitmap
' Step 1
' Grab the current image (the latest version of the chart)

Dim bm As New Bitmap(PicBox.Width, PicBox.Height)
Dim gr As Graphics = Graphics.FromImage(bm)
' Step 2
' Get the total height available and split it into chunks

Dim total As Integer = PicBox.Height
Dim chunk As Single = total / chunks
' Tack missing guidelines to right hand side on the Graphics object.
 For i As Single = chunk To total Step chunk
   gr.DrawLine(Pens.Green, PicBox.Width - XMove, i, PicBox.Width, i)
 Next i
' Step 3
' Draw this grabbed image, placing it XMove pixels to the left
 If Not IsNothing(PicBox.Image) Then
   gr.DrawImage(PicBox.Image, -XMove, 0)
 End If
' Step 4
' Plot the new value.
' Calculate the scaling required to make full use of the height of
' the PictureBox
Dim ValueRange As Single = Max - Min
Dim vScale As Single = PicBox.Height / ValueRange
' Apply the scale to the current value
NewValue *= vScale
' Step 5
' Shift start point from top left to bottom left.

gr.TranslateTransform(0, PicBox.Height)
' Step 6
' Draw the next line segment on the Graphics object.
' If Min is > 0 then you need to shift the drawing down once again,
' this time to put the Min value on the horizontal axis

If Min > 0 Then gr.TranslateTransform(0, Min * vScale)
Dim p As Pen = New Pen(Color.Navy, 2)
gr.DrawLine(p, PicBox.Width - 1 - XMove, -OldValue, PicBox.Width - 1, -NewValue)
OldValue = NewValue
' Step 7
' Return the Bitmap .

Return bm
' Step 8
' All done

End Function

Private Sub _3rd_More_Generic_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Resize
' Paint Guidelines on picGraph

picGraph.Image = DisplayGuidelines(picGraph, Chunks)
' Paint Guidelines and Numbers on picValues

picValues.Image = Me.DisplayVerticalValues(picValues, Chunks, SBUserValue.Minimum, SBUserValue.Maximum)
End Sub

    If you use a Horizontal ScrollBar as we did in the previous article, then this code will cause the above procedures to draw the chart:

Private Sub SBUserValue_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles SBUserValue.ValueChanged
  picGraph.Image = DisplayGuidelinesAndChart(picGraph, Chunks, XMove,
  Me.SBUserValue.Value, SBUserValue.Minimum, SBUserValue.Maximum)
  lblValue.Text = SBUserValue.Value.ToString
End Sub


Line Chart - Printing

Printing the Chart As-Is

   Let's take the simplest case and print the chart that the user can see on the screen (but which we know consists of two PictureBoxes side by side).   The obvious way of approaching this is to merge the images of the two PictureBoxes into one single image and then Draw this image to the printer.

  The following procedure will merge two images that are passed in as parameters:

Public Function MergeImages(ByVal Pic1 As Image, ByVal pic2 As Image) As Image
  Dim MergedImage As Image ' This will be the finished merged image
  Dim Wide, High As Integer ' The Size of the merged image
  ' Calculate Width and Height needed for composite image Width:
  Wide = Pic1.Width + pic2.Width
  ' Height: Ensure that the new image is high enough for both images
  ' that we plan to place inside it.
  ' Not necessary in this case, but useful if you use the merge technique
  ' in future where the images are different heights.

    If Pic1.Height >= pic2.Height Then
     High = Pic1.Height
     High = pic2.Height
    End If
  ' Create an empty Bitmap the correct size to hold both images side by side
  Dim bm As New Bitmap(Wide, High)
  ' Get the Graphics object for this bitmap
  Dim gr As Graphics = Graphics.FromImage(bm)
  ' Draw a black line round the outside (looks better when printed)
  gr.DrawRectangle(Pens.Black, 0, 0, Wide - 1, High - 1)
  ' Draw the first source image at left side of new image
  gr.DrawImage(Pic1, 0, 0)
  ' Draw second source image, offset to the right edge of first source image
  gr.DrawImage(pic2, Pic1.Width, 0)
  ' Assign the merged bitmap you have just created as the image you are going
  ' to return for printing

  MergedImage = bm
  ' Finished with the Graphics object
  ' You now have an Image named MergedImage which you can print.
 Return MergedImage
End Function

   Now that you have the means to create a merged image, you simply use the same approach as we did for the bar chart printout.  That is, we use the PrintPage event of a PrintDocument control, get an image, create a rectangle of a suitable size and Draw the image within that rectangle on to the printer page.

   Drag a PrintDocument control on to the form, name it "PrintImageDocument" and put this code in its PrintPage event.  The code is very similar to the previous chart's example:

PrivateSub PrintImageDocument_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintImageDocument.PrintPage
  ' First create the image using the function prepared above:
 Dim ImageToPrint As Image = MergeImages(picValues.Image, picGraph.Image)
  ' Now Print it - change "20, 20" to whatever X and Y position values
  ' you want as your start point for printing on the page
 Dim R As New Rectangle(20, 20, ImageToPrint.Width, ImageToPrint.Height)
 e.Graphics.DrawImage(ImageToPrint, R)
End Sub


  If you place a button named BtnPrint on the form, you can use the following code in the Click event:

Private Sub BtnPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnPrint.Click
End Sub



Some Printing Options

Preview Before Printing

   When we printed the chart images for the Bar Chart and the Line Chart, we manually set the X and Y locations of the Rectangle inside which we drew/printed that image to paper.   I used 20, 20 just to make sure that there was a reasonable gap from the left and the  top of the paper.   In both those cases this caused the chart to be printed  in the top left hand corner of the page, leaving quite a lot of white space at the right hand side.   It may be that sometimes you want to see what the finished printout will look like  - and also you may want to change the location of the chart on the printed page.  

   The most straightforward way of doing this is to drag a PrintPreviewDialog control on to the form.(Not to be confused with the very similarly named PrintPreviewControl control  - no wonder people find this stuff hard to master at first!).    You can leave the control with its default name of PrintPreviewDialog1.

   Add a second button to the form and call this one BtnPreview.   The following code in the Button's click event will display the chart in a preview pane and give the user the opportunity of confirming the print or cancelling it.  

Private Sub BtnPreview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnPreview.Click
   PrintPreviewDialog1.Document = PrintImageDocument
End Sub

 The PrintPreviewDialog also allows the user to zoom in on the page and this is something you may need to do to check that all is well; the default display is sometimes a rather scrappy thumbnail version that gives the impression that parts of the chart won't print.  

 Zooming in will reassure you that all's well. 


Centre the Printout

    Of course it's not much help to you to see in the Preview Pane that the chart will be printed over to the left and you'd like it to be centred.   There isn't a facility in the PrintPreviewDialog to make those kind of changes.  

   However, you can use relatively simple code to calculate where the X and Y points of the rectangle should be placed so that the chart is centred.

   Although we could tweak the PrintDocument control we first used (PrintImageDocument) I think it will be better to use a separate one so that you can cherry pick if you simply want to copy and paste from either variation.   So, drag a second PrintDocument on to the form and name it PrintCentred.

   In the PrintPage event of this control, we first create the merged image as before:

Private Sub PrintCentred_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintCentred.PrintPage
   ' First create the image using the function prepared above:
  Dim ImageToPrint As Image = MergeImages(picValues.Image, picGraph.Image)

   Then set default values for the X and Y points of the rectangle inside which we draw the chart on the paper.

Dim X As Integer = 20
Dim Y As Integer = 20

  Test if the image to be printed is no wider than the width of the paper it's to be printed on.   If the image is smaller then calculate where it should be placed on the printed page to centre it horizontally. 

With PrintCentred.DefaultPageSettings.PaperSize
   ' Only do this if image is smaller than the paper width
    If ImageToPrint.Width < .Width Then
      X = CInt((.Width - ImageToPrint.Width) / 2)
    End If
End With

   Draw the image on to the paper using either the default X, Y values or the new value of X if this has been changed above.

Dim R As New Rectangle(X, Y, ImageToPrint.Width, ImageToPrint.Height)

e.Graphics.DrawImage(ImageToPrint, R)

   I probably should mention that it would take only a couple of additional lines of code to centre the chart vertically also.   Simply test the Height of the Image vs. the height of the paper and if the image is smaller than the paper then use the same logic as above to set the value of Y correctly.

   In order to print the page, drag a third button on to the form, name it BtnCentrePrint.  This code in its click event will call the PrintPreviewDialog (we can use the same one).  As you will see when you view the preview, the chart is now centred on the page.


    There are many more tweaks you can make to the printout and I hope to cover those in a future article.  For now though you have the ability to print hard copies of any of the three kinds of charts (plus other variations you create yourself ) with very little extra effort.

   I have created a VB.NET 2003* version of the code for this article and you can download and try out the demo solution for yourself.

  (* As the original articles were written at the time when VB.NET 2003 was the current version.   The same approach will work just as well in VB 2005.)