(This is the fourth and final part of the Paint-by-Numbers series)
Late last year, some clever guys on our Visual Basic team released the PrintForm component on the web. The idea behind the PrintForm component was to make printing and previewing your form very easy in .NET. I’m going to leverage this component to enable printing of the Paint-by-Number puzzles created in the application I’ve been building in this series.
First of all, I need to download the component, which is available here on the Microsoft site – it’s about 500KB. Once downloaded, I’ll install it (you can even do this while Visual Studio is open), and now I can add the PrintForm control in the component to my toolbox. With my form selected, I right-click on the “Printing” category in the toolbox and select “Choose Items…” In the resulting dialog, I’ll check the box next to “Print Form” on the .NET tab, and that’ll do it once I press “OK.”
Now, I’ll drag an instance of the PrintForm control to the component tray and rename it to be “VBPBNPrintForm.” We don’t need to change any properties on it otherwise, so on the form, I’ll drop down the File menu item and double-click on “Print Preview” to generate its event handler for that command – now I can start coding!
The PrintForm command really just has one important verb – Print. It can take several options, but overall it’s just a print job to either a printer, a file, or a preview window. Which of those it does depends on the value you assign to its PrintAction property. In this case, I want to just preview the document, and so I’ll need to set it to “PrintToPreview.” The code looks like this:
Private Sub PrintPreviewToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles PrintPreviewToolStripMenuItem.Click
Me.VBPBNPrintForm.PrintAction = Printing.PrintAction.PrintToPreview
Me.VBPBNPrintForm.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly)
End Sub
Note that I’ve used an overload of Print which tells the control which form to print (which is my main form in this case), and also indicates that I just want to print the client area and not the window borders and so on. However, if you run this, you’ll notice that the menu strip is printing in the preview! This is because it’s in the client area. Print doesn’t use document technology – it will in fact print anything visible in the client area, which works to our benefit when printing a puzzle solution (View/Show Solution is checked) or just the puzzle (View/Show Solution is unchecked), but not in this case. So, for the duration of the printing job, I’ll simply hide the Menu, and I’ll wrap that in a try/catch/finally so that the menu will always get turned back on even if an exception is thrown from printing. (This is hackery, plain and simple, but the benefits are enough to outweigh any sense of guilt I might feel about that.) Now the code looks like this:
Private Sub PrintPreviewToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles PrintPreviewToolStripMenuItem.Click
Try
Me.VBPBNMenuStrip.Visible = False
Me.VBPBNPrintForm.PrintAction = Printing.PrintAction.PrintToPreview
Me.VBPBNPrintForm.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly)
Catch ex As Exception
Finally
Me.VBPBNMenuStrip.Visible = True
End Try
End Sub
and that will work nicely. You can zoom in and out, resize the display, and even print to the default printer from the dialog that pops up.
Now, for the actual Print command from the file menu, I could write very similar code (the one change is underlined below):
Private Sub PrintToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PrintPreviewToolStripMenuItem.Click
Try
Me.VBPBNPrintForm.PrinterSettings = Me.VBPBNPrintDialog.PrinterSettings
Me.VBPBNMenuStrip.Visible = False
Me.VBPBNPrintForm.PrintAction = Printing.PrintAction.PrintToPrinter
Me.VBPBNPrintForm.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly)
Catch ex As Exception
Finally
Me.VBPBNMenuStrip.Visible = True
End Try
End Sub
But that would only allow us to print to the default printer, and I want to be a little more sophisticated. I’ll drag out a PrintDialog control (which I’ll rename VBPBNPrintDialog) and use it to capture settings before doing the actual printing. The PrintForm control uses the same PrinterSettings object that the PrintDialog does, which makes things very easy – I just assign one to the other before doing the actual printing. Then, I check to see if we’re printing to a file or to a printer. If printing to a printer, then I have everything I need; if a file, I’ll need to pop up a “save file” dialog to get the filename from the user first. The code now looks like this:
Private Sub PrintToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles PrintToolStripMenuItem.Click
‘ Bring up the print dialog:
Dim res As DialogResult = Me.VBPBNPrintDialog.ShowDialog
If res = Windows.Forms.DialogResult.OK Then
Try
‘ Copy the settings from the dialog
Me.VBPBNPrintForm.PrinterSettings = Me.VBPBNPrintDialog.PrinterSettings
‘ Turn off the menu strip
Me.VBPBNMenuStrip.Visible = False
If Me.VBPBNPrintForm.PrinterSettings.PrintToFile = True Then
‘ The user wants to print to a file, so bring up a “Save” dialog and get the file name
Me.VBPBNSaveFileDialog.Title = My.Resources.TITLE_SavePrintFile
Me.VBPBNSaveFileDialog.Filter = My.Resources.FILT_PrintFilter
If Me.VBPBNSaveFileDialog.ShowDialog() = Windows.Forms.DialogResult.OK Then
‘ Go ahead & print to the file
Me.VBPBNPrintForm.PrintFileName = Me.VBPBNSaveFileDialog.FileName
Me.VBPBNPrintForm.PrintAction = Printing.PrintAction.PrintToFile
Me.VBPBNPrintForm.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly)
End If
Else
‘ Print to whatever printer was selected
Me.VBPBNPrintForm.PrintAction = Printing.PrintAction.PrintToPrinter
Me.VBPBNPrintForm.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly)
End If
Catch ex As Exception
Finally
‘ Make sure that the menu turns back on!
Me.VBPBNMenuStrip.Visible = True
End Try
End If
End Sub
and we’re done with the Print code. (The resource FILT_PrintFilter is similar to the other filter I used for saving files in the previous post, but aimed at .EPS files instead of .VBPBN files, and TITLE_SavePrintFile is just a title for the window.)
But, hold on – the preview can also use the PrinterSettings object. For example, if the user chose “Print,” clicked “Apply” after changing some settings, but then chose Cancel, the settings would persist for the next call to Print, so we’d expect those to apply to the preview as well (otherwise, it wouldn’t be much of a preview, since there’s no other way to do settings in a preview). So, we’ll add one more line to the Print Preview handler (underlined):
Private Sub PrintPreviewToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PrintPreviewToolStripMenuItem.Click
Try
Me.VBPBNPrintForm.PrinterSettings = Me.VBPBNPrintDialog.PrinterSettings
Me.VBPBNMenuStrip.Visible = False
Me.VBPBNPrintForm.PrintAction = Printing.PrintAction.PrintToPreview
Me.VBPBNPrintForm.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly)
Catch ex As Exception
Finally
Me.VBPBNMenuStrip.Visible = True
End Try
End Sub
And that’s it!
PrintForm is a great alternative to fiddling about with print documents if you don’t need to do a lot of reformatting for the printout you generate. (In this case, all I had to do was hide a menu strip temporarily, which is much easier that defining a print document , etc.) The final application is attached to this post. I hope you’ve enjoyed this series – it was a lot of fun to write, and already I’m thinking about the improvements that could be made, such as arbitrarily-sized grids, select/copy/paste of arbitrary bitmaps into and within the puzzle, creating a “puzzle viewer” to solve the puzzle on the PC rather than on paper, adding authoring information to the saved puzzle, supporting the red/black Paint-by-Number puzzles that have been so popular recently, and so on – it’s hard to stop!
I’ll be around for another week or so to answer any comments on these posts, but after that I’ll be on a month-long vacation (ahhhhhh…. J), after which I’ll be digging out of e-mail for days, so I’m going to be silent for a bit. I hope everyone enjoys their summer (or winter, depending on where you are), and I’ll probably post again at the end of August. I hope to come up with a lot of blog application ideas as my family and I drive around the east coast of the U.S. ‘Til then…
–Matt–*
0 comments
Be the first to start the discussion.