How to Take a Screenshot of Your wxPython App and Print it

Have you ever thought that it would be cool to have your wxPython code take a screenshot of itself? Well, Andrea Gavana figured out a cool way to do just that and between what he told us on the wxPython mailing list and what I learned from other sources, you will soon learn how to not only take the screenshot, but how to send it to your printer!

Let’s take a look at how to take the screenshot first:

import sys
import wx
import snapshotPrinter
 
class MyForm(wx.Frame):
 
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(500,500))
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        screenshotBtn = wx.Button(panel, wx.ID_ANY, "Take Screenshot")
        screenshotBtn.Bind(wx.EVT_BUTTON, self.onTakeScreenShot)
        printBtn = wx.Button(panel, label="Print Screenshot")
        printBtn.Bind(wx.EVT_BUTTON, self.onPrint)
        
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(screenshotBtn, 0, wx.ALL|wx.CENTER, 5)
        sizer.Add(printBtn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

    def onTakeScreenShot(self, event):
        """ Takes a screenshot of the screen at give pos & size (rect). """
        print 'Taking screenshot...'
        rect = self.GetRect()
        # see http://aspn.activestate.com/ASPN/Mail/Message/wxpython-users/3575899
        # created by Andrea Gavana
        
        # adjust widths for Linux (figured out by John Torres 
        # http://article.gmane.org/gmane.comp.python.wxpython/67327)
        if sys.platform == 'linux2':
            client_x, client_y = self.ClientToScreen((0, 0))
            border_width = client_x - rect.x
            title_bar_height = client_y - rect.y
            rect.width += (border_width * 2)
            rect.height += title_bar_height + border_width

        #Create a DC for the whole screen area
        dcScreen = wx.ScreenDC()

        #Create a Bitmap that will hold the screenshot image later on
        #Note that the Bitmap must have a size big enough to hold the screenshot
        #-1 means using the current default colour depth
        bmp = wx.EmptyBitmap(rect.width, rect.height)

        #Create a memory DC that will be used for actually taking the screenshot
        memDC = wx.MemoryDC()

        #Tell the memory DC to use our Bitmap
        #all drawing action on the memory DC will go to the Bitmap now
        memDC.SelectObject(bmp)

        #Blit (in this case copy) the actual screen on the memory DC
        #and thus the Bitmap
        memDC.Blit( 0, #Copy to this X coordinate
                    0, #Copy to this Y coordinate
                    rect.width, #Copy this width
                    rect.height, #Copy this height
                    dcScreen, #From where do we copy?
                    rect.x, #What's the X offset in the original DC?
                    rect.y  #What's the Y offset in the original DC?
                    )

        #Select the Bitmap out of the memory DC by selecting a new
        #uninitialized Bitmap
        memDC.SelectObject(wx.NullBitmap)

        img = bmp.ConvertToImage()
        fileName = "myImage.png"
        img.SaveFile(fileName, wx.BITMAP_TYPE_PNG)
        print '...saving as png!'
        
    #----------------------------------------------------------------------
    def onPrint(self, event):
        """
        Send screenshot to the printer
        """
        printer = snapshotPrinter.SnapshotPrinter()
        printer.sendToPrinter()
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

This piece of code create a fairly large frame with two buttons in it. Yes, it’s pretty boring, but this is a demo, not an art show. The part we care about most is the onTakeScreenShot method. If you go to the first commented link, you’ll find Andrea Gavana’s original version of this script. We added a conditional from John Torres in there that makes this script behave better on Linux since it was originally written for Windows. The comments tell the story of the code, so take your time reading them and when you’re done, we can move on to how we can send our result to the printer.

The Snapshot Printer Script

#######################################################################
# snapshotPrinter.py
#
# Created: 12/26/2007 by mld
#
# Description: Displays screenshot image using html and then allows
#              the user to print it.
#######################################################################

import os
import wx
from wx.html import HtmlEasyPrinting, HtmlWindow

class SnapshotPrinter(wx.Frame):
    
    #----------------------------------------------------------------------
    def __init__(self, title='Snapshot Printer'):
        wx.Frame.__init__(self, None, wx.ID_ANY, title, size=(650,400))

        self.panel = wx.Panel(self, wx.ID_ANY)
        self.printer = HtmlEasyPrinting(name='Printing', parentWindow=None)
        
        self.html = HtmlWindow(self.panel)
        self.html.SetRelatedFrame(self, self.GetTitle())

        if not os.path.exists('screenshot.htm'):
            self.createHtml()
        self.html.LoadPage('screenshot.htm')

        pageSetupBtn = wx.Button(self.panel, wx.ID_ANY, 'Page Setup')
        printBtn = wx.Button(self.panel, wx.ID_ANY, 'Print')
        cancelBtn = wx.Button(self.panel, wx.ID_ANY, 'Cancel')

        self.Bind(wx.EVT_BUTTON, self.onSetup, pageSetupBtn)
        self.Bind(wx.EVT_BUTTON, self.onPrint, printBtn)
        self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        
        sizer.Add(self.html, 1, wx.GROW)
        btnSizer.Add(pageSetupBtn, 0, wx.ALL, 5)
        btnSizer.Add(printBtn, 0, wx.ALL, 5)
        btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
        sizer.Add(btnSizer)
        
        self.panel.SetSizer(sizer)
        self.panel.SetAutoLayout(True)

    #----------------------------------------------------------------------
    def createHtml(self):
        '''
        Creates an html file in the home directory of the application
        that contains the information to display the snapshot
        '''
        print 'creating html...'
        
        html = '\n\n
\n\n' f = file('screenshot.htm', 'w') f.write(html) f.close() #---------------------------------------------------------------------- def onSetup(self, event): self.printer.PageSetup() #---------------------------------------------------------------------- def onPrint(self, event): self.sendToPrinter() #---------------------------------------------------------------------- def sendToPrinter(self): """""" self.printer.GetPrintData().SetPaperId(wx.PAPER_LETTER) self.printer.PrintFile(self.html.GetOpenedPage()) #---------------------------------------------------------------------- def onCancel(self, event): self.Close() class wxHTML(HtmlWindow): #---------------------------------------------------------------------- def __init__(self, parent, id): html.HtmlWindow.__init__(self, parent, id, style=wx.NO_FULL_REPAINT_ON_RESIZE) if __name__ == '__main__': app = wx.App(False) frame = SnapshotPrinter() frame.Show() app.MainLoop()

This little script uses the HtmlWindow widget and the HtmlEasyPrinting method to send something to the printer. Basically, you can create some really simple HTML code (see the createHtml method) and then use the HtmlWindow to view it. Next you use HtmlEasyPrinting to send it to a printer. It will actually display the printer dialog and let you choose which printer you want to send the document to.

Well, I hope you have found this article helpful in your programming. I hope to hear from you in the comments!

2 thoughts on “How to Take a Screenshot of Your wxPython App and Print it”

  1. Good Job! I like this post, Thanks 😉

    P.S.:
    “client_x, client_y = window.ClientToScreen((0, 0))” should be “client_x, client_y = self.ClientToScreen((0, 0))”

Comments are closed.