Introduction
The Printer framework for wxWindows / wxPython is quite complicated. Because of this, (wx)HtmlEasyPrinting is provided to help simplify basic printing functionality. This Recipe covers a simplified approach to using both (wx)HtmlEasyPrinting and the more complicated (wx)Printout.
Conventions
When referring to a wxPython class, I use (wx) in front of it because the 'wx' prefix is optional, depending on which namespace you use.
For instance, classes from 'wxPython.wx' will be prefixed with the 'wx', while classes from the 'wx' namespace will not have the 'wx' prefix.
For example:
from wxPython.wx import wxFrame
is equivalent to:
from wx import Frame
in wxPython versions 2.4.1.2 and higher.
Any versions prior to 2.4.1.2 will need to stick with from wxPython.wx import wxFrame style syntax.
All code will be using the new namespace, but it is easy to convert back if desired.
Change all from wx import a,b,c to be from wxPython.wx import wxa, wxb, wxc
- Change all uses of wxPython classes a,b,c to be wxa, wxb, wxc (where wxa, wxb, and wxc are place holders for wxPython classes)
What Objects are Involved
The following classes will be used in these recipes:
(wx)HtmlEasyPrinting -- The simple way to print
(wx)Printout -- Base printing class that will be inherited from. Various control functions handling printing events are defined here.
(wx)PrintData -- Printer / page configuration data
(wx)PrintDialogData -- (same as (wxPrintData) except what is used by Page Setup window??)
(wx)Printer -- Software interface to the printer
(wx)MessageBox -- Used for alerting the user to any problems
(wx)PrintPreview -- Used for print preview
(wx)PrintDialog -- Window that pops up, asking how many pages to print, etc..
Special Concerns or Comments about the Easy Printing approach
- Very simple to use.
- As powerful as HTML
- Seems to be limited around the capabilities of HTML (e.g. seems to have less control over printing configuration. Please correct me if I'm wrong).
Change the GetHtmlText code to be more appropriate with the kind of text you will be using.
Code Sample - Easy Printing
1 from wx.html import HtmlEasyPrinting
2
3 class Printer(HtmlEasyPrinting):
4 def __init__(self):
5 HtmlEasyPrinting.__init__(self)
6
7 def GetHtmlText(self,text):
8 "Simple conversion of text. Use a more powerful version"
9 html_text = text.replace('\n\n','<P>')
10 html_text = text.replace('\n', '<BR>')
11 return html_text
12
13 def Print(self, text, doc_name):
14 self.SetHeader(doc_name)
15 self.PrintText(self.GetHtmlText(text),doc_name)
16
17 def PreviewText(self, text, doc_name):
18 self.SetHeader(doc_name)
19 HtmlEasyPrinting.PreviewText(self, self.GetHtmlText(text))
Special Concerns or Comments about the (wx)Printout Printing approach
Using (wx)Printout is more powerful, and allows for finer control of the printouts
- See the Python Cookbook for ways to retrieve detailed error information.
- On windows 98, a run-time can occur when closing out of the Print Preview window. This is being looked into at this time.
- The code is not flexible in terms of how many pages can be printed out. Additionally color, headers, graphics, and footers are not handled.
I am not sure which functions need to be defined, and which ones are optional. My suspicion is that HasPage, GetPageInfo, and OnPrintPage are required.
Code Sample - `(wx)Printout` Printing
#License: MIT
from wx import Printout, PrintData, PAPER_LETTER, PrintDialogData
from wx import Printer as wxPrinter, MessageBox, PrintPreview, PrintDialog
def GetErrorText():
"Put your error text logic here. See Python Cookbook for a useful example of error text."
return "Some error occurred."
class Printer(Printout):
def __init__(self, frame):
"Prepares the Printing object. Note: change current_y for 1, 1.5, 2 spacing for lines.
Printout.__init__(self)
self.printer_config = PrintData()
self.printer_config.SetPaperId(PAPER_LETTER)
self.frame = frame
self.doc_text = ''
self.doc_name = ''
self.current_y = 15 #y should be either (15, 22, 30)
if self.current_y == 15:
self.num_lines_per_page = 50
elif self.current_y == 22:
self.num_lines_per_page = 35
else:
self.num_lines_per_page = 25
def Print(self, text, doc_name):
"Prints the given text. Currently doc_name logic doesn't exist. E.g. might be useful for a footer.."
self.doc_text = text
self.doc_name = doc_name
pdd = PrintDialogData()
pdd.SetPrintData(self.printer_config)
printer = wxPrinter(pdd)
if not printer.Print(self.frame,self):
MessageBox("Unable to print the document.")
else:
self.printer_config = wx.PrintData(printer.GetPrintDialogData().GetPrintData())
def PreviewText(self, text, doc_name):
"This function displays the preview window for the text with the given header."
try:
self.doc_name = doc_name
self.doc_text = text
#Destructor fix by Peter Milliken --
print1 = Printer(self.frame, text = self.doc_text)
print2 = Printer(self.frame, text = self.doc_text)
preview = PrintPreview(print1, print2, self.printer_config)
#preview = PrintPreview(self,self,self.printer_config)
if not preview.Ok():
MessageBox("Unable to display preview of document.")
return
preview_window = PreviewFrame(preview, self.frame, \
"Print Preview - %s" % doc_name)
preview_window.Initialize()
preview_window.SetPosition(self.frame.GetPosition())
preview_window.SetSize(self.frame.GetSize())
preview_window.MakeModal(True)
preview_window.Show(True)
except:
MessageBox(GetErrorText())
def PageSetup(self):
"This function handles displaying the Page Setup window and retrieving the user selected options."
config_dialog = wxPrintDialog(self.frame)
config_dialog.GetPrintDialogData().SetPrintData(self.printer_config)
config_dialog.GetPrintDialogData().SetSetupDialog(True)
config_dialog.ShowModal()
self.printer_config = config_dialog.GetPrintDialogData().GetPrintData()
config_dialog.Destroy()
def OnBeginDocument(self,start,end):
"Do any end of document logic here."
self.base_OnBeginDocument(start,end)
def OnEndDocument(self):
"Do any end of document logic here."
self.base_OnEndDocument()
def OnBeginPrinting(self):
"Do printing initialization logic here."
self.base_OnBeginPrinting()
def OnEndPrinting(self):
"Do any post printing logic here."
self.base_OnEndPrinting()
def OnPreparePrinting(self):
"Do any logic to prepare for printing here."
self.base_OnPreparePrinting()
def HasPage(self, page_num):
"This function is called to determine if the specified page exists."
return len(self.GetPageText(page_num)) > 0
def GetPageInfo(self):
"""
This returns the page information: what is the page range available, and what is the selected page range.
Currently the selected page range is always the available page range. This logic should be changed if you need
greater flexibility.
"""
minPage = 1
maxPage = int(len(self.doc_text.split('\n'))/self.num_lines_per_page) + 1
fromPage, toPage = minPage, maxPage
return (minPage,maxPage,fromPage,toPage)
def OnPrintPage(self, page_num):
"This function / event is executed for each page that needs to be printed."
dc = self.GetDC()
x,y = 25, self.current_y
if not self.IsPreview():
y *=4
line_count = 1
for line in self.GetPageText(page_num):
dc.DrawText(line, x, y*line_count)
line_count += 1
return True
def GetPageText(self, page_num):
"This function returns the text to be displayed for the given page number."
lines = self.doc_text.split('\n')
lines_for_page = lines[(page_num -1)*self.num_lines_per_page: page_num*(self.num_lines_per_page-1)]
return lines_for_page
Using the examples above
Both classes have the same interface for PrintPreview, PageSetup, and PrintText, so using them is pretty straightforward:
Save the class in Printer.py
from Printer import Printer
#Other code here..
#Inside of frame initialization put:
self.printer = Printer(self)
#To print, e.g. After binding an event to OnPrint
def OnPrint(self, event):
#replace self.editor.text and self.editor.title with appropriate values.
self.printer.Print(self.editor.text,self.editor.title)
#To print preview, e.g. After binding an event to OnPrintPreview
def OnPrintPreview(self, event):
#replace self.editor.text and self.editor.title with appropriate values.
self.printer.PreviewText(self.editor.text, self.editor.title)
#To display the page setup dialog, e.g. after binding an event to OnPageSetup
def OnPageSetup(self, event):
self.printer.PageSetup()
Comments
Date Person (bot) Comments
Sean McKay Originally Created
7/31/03 Sean McKay Applied bug fix suggested by Peter Milliken for the Print Preview call.
8/27/03 Sean McKay Fixed a typo in the OnPageSetup function definition.
12/05/03 Chris Barker Put a bunch of back quotes around `wxClasses` so that they don't get displayed as links to other Wiki pages
01/22/04 Claudio Succa Removed 'return' from 'return self.base_OnBeginDocument(start,end)' in section 'Code Sample (wx)Printout'Please put any feedback or suggestions here...
Suggestions
One might want to benefit from the new Configuration Dialog where it is possible to setup margins and more. The following change applies to Printer.py where it should replace the PageSetup method (this still uses 2.4.0 names, sorry):
def PageSetup(self):
"""
This function handles displaying the Page Setup window and
retrieving the user selected options.
It's been updated to use the new style Windows, which allow
more options to be configured.
"""
config_dialog = wxPageSetupDialog(self.frame)
config_dialog.GetPageSetupData()
config_dialog.ShowModal()
self.printer_config = config_dialog.GetPageSetupData()
config_dialog.Destroy()I am having a hard time implementing the code as written here. It would be nice if this page had code that was easily usable by newbies like myself with 2.8. I made some changes but the Page Setup dialog still will not read data. There is a problem with the suggestion above in that it changes the type of the config data. Here is my code, please fix and edit as needed.
#License: MIT
import wx
from wx import Printer as wxPrinter
def GetErrorText():
"Put your error text logic here. See Python Cookbook for a useful example of error text."
return "Some error occurred."
class Printer(wx.Printout):
def __init__(self, frame, text = "", name = ""):
"Prepares the Printing object. Note: change current_y for 1, 1.5, 2 spacing for lines."
wx.Printout.__init__(self)
self.printer_config = wx.PrintData()
self.printer_config.SetPaperId(wx.PAPER_LETTER)
self.printer_config.SetOrientation(wx.LANDSCAPE)
self.frame = frame
self.doc_text = text
self.doc_name = name
self.current_y = 15 #y should be either (15, 22, 30)
if self.current_y == 15:
self.num_lines_per_page = 50
elif self.current_y == 22:
self.num_lines_per_page = 35
else:
self.num_lines_per_page = 25
def Print(self, text, doc_name):
"Prints the given text. Currently doc_name logic doesn't exist. E.g. might be useful for a footer.."
self.doc_text = text
self.doc_name = doc_name
pdd = wx.PrintDialogData()
pdd.SetPrintData(self.printer_config)
printer = wxPrinter(pdd)
if not printer.Print(self.frame,self):
wx.MessageBox("Unable to print the document.")
else:
self.printer_config = printer.GetPrintDialogData().GetPrintData()
def PreviewText(self, text, doc_name):
"This function displays the preview window for the text with the given header."
#try:
self.doc_name = doc_name
self.doc_text = text
#Destructor fix by Peter Milliken -- with change to Printer.__init__()
print1 = Printer(self.frame, text = self.doc_text, name = self.doc_name)
print2 = Printer(self.frame, text = self.doc_text, name = self.doc_name)
preview = wx.PrintPreview(print1, print2, self.printer_config)
if not preview.Ok():
wx.MessageBox("Unable to display preview of document.")
return
preview_window = wx.PreviewFrame(preview, self.frame, \
"Print Preview - %s" % doc_name)
preview_window.Initialize()
preview_window.SetPosition(self.frame.GetPosition())
preview_window.SetSize(self.frame.GetSize())
preview_window.MakeModal(True)
preview_window.Show(True)
#except:
# wx.MessageBox(GetErrorText())
def PageSetup(self):
"This function handles displaying the Page Setup window and retrieving the user selected options."
config_dialog = wx.PageSetupDialog(self.frame) #replaces PrintDialog
config_dialog.GetPageSetupData()
print self.printer_config
config_dialog.ShowModal()
self.printer_config = config_dialog.GetPageSetupData()
print self.printer_config
config_dialog.Destroy()
def OnBeginDocument(self,start,end):
"Do any end of document logic here."
wx.Printout.OnBeginDocument(self,start,end)
def OnEndDocument(self):
"Do any end of document logic here."
wx.Printout.OnEndDocument(self)
def OnBeginPrinting(self):
"Do printing initialization logic here."
wx.Printout.OnBeginPrinting(self)
def OnEndPrinting(self):
"Do any post printing logic here."
wx.Printout.OnEndPrinting(self)
def OnPreparePrinting(self):
"Do any logic to prepare for printing here."
wx.Printout.OnPreparePrinting(self)
def HasPage(self, page_num):
"This function is called to determine if the specified page exists."
return len(self.GetPageText(page_num)) > 0
def GetPageInfo(self):
"""
This returns the page information: what is the page range available, and what is the selected page range.
Currently the selected page range is always the available page range. This logic should be changed if you need
greater flexibility.
"""
minPage = 1
maxPage = int(len(self.doc_text.split('\n'))/self.num_lines_per_page)
fromPage, toPage = minPage, maxPage
return (minPage,maxPage,fromPage,toPage)
def OnPrintPage(self, page_num):
"This function / event is executed for each page that needs to be printed."
dc = self.GetDC()
x,y = 25, self.current_y
if not self.IsPreview():
y *=4
line_count = 1
for line in self.GetPageText(page_num):
dc.DrawText(line, x, y*line_count)
line_count += 1
return True
def GetPageText(self, page_num):
"This function returns the text to be displayed for the given page number."
lines = self.doc_text.split('\n')
lines_for_page = lines[(page_num -1)*self.num_lines_per_page: page_num*(self.num_lines_per_page-1)]
return lines_for_page