The other day I was working on a project where I was using the fabulous ObjectListView widget (a wrapper around wx.ListCtrl) and I wanted to add the ability to double-click an item in the control to make it open a PDF. I knew I had read somewhere on the internet about how do this sort of thing, but it was once again a drag to find that information. So now that I know, I decided to share it this time. I’ll also show you how to open a PDF file on Windows as a bonus!

Digging into the Code

wxOLVDoubleclick

Working with the ObjectListView widget is pretty easy. I’ve talked about it in the past, so if you want you can go check out those previous article which I’ll link to at the end of this one. Anyway, I always find it easier to show the code and then explain what’s going on, so let’s do that here too:

import glob
import os
import subprocess
import wx
 
from ObjectListView import ObjectListView, ColumnDefn
 
########################################################################
class File(object):
    """
    Model of the file object
    """
 
    #----------------------------------------------------------------------
    def __init__(self, path):
        """Constructor"""
        self.filename = os.path.basename(path)
        self.path = path
 
########################################################################
class UIPanel(wx.Panel):
    """
    Panel class
    """
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        self.base_path = os.path.dirname(os.path.abspath(__file__))
        self.data = []
 
        # -----------------------------------------------
        # create the widgets
 
        # add the data viewing control
        self.pdfOlv = ObjectListView(self, 
                                     style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        self.pdfOlv.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onDoubleClick)
        self.pdfOlv.SetEmptyListMsg("No PDFs Found!")
        self.updateDisplay()
 
        browseBtn = wx.Button(self, label="Browse")
        browseBtn.Bind(wx.EVT_BUTTON, self.getPdfs)
 
        # -----------------------------------------------
        # layout the widgets
        mainSizer = wx.BoxSizer(wx.VERTICAL)
 
        mainSizer.Add(self.pdfOlv, 1, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(browseBtn, 0, wx.ALL|wx.CENTER, 5)
 
        self.SetSizer(mainSizer)
 
    #----------------------------------------------------------------------
    def getPdfs(self, event):
        """
        Attempts to load PDFs into objectlistview
        """
        self.data = []
 
        dlg = wx.DirDialog(self, "Choose a directory:",
                          style=wx.DD_DEFAULT_STYLE)
        res = dlg.ShowModal()
        if res != wx.ID_OK:
            return
        path = dlg.GetPath()
        dlg.Destroy()
 
        pdfs = glob.glob(path + "/*.pdf")
 
        if pdfs:
            for pdf in pdfs:
                self.data.append(File(pdf))
 
            self.updateDisplay()
 
    #----------------------------------------------------------------------
    def onDoubleClick(self, event):
        """
        Opens the PDF that is double-clicked
        """
        obj = self.pdfOlv.GetSelectedObject()
        print "You just double-clicked on %s" % obj.path
        cmd = os.getenv("comspec")
        acrobat = "acrord32.exe"
        pdf = obj.path
 
        cmds = [cmd, "/c", "start", acrobat, "/s", pdf]
        subprocess.Popen(cmds)
 
    #----------------------------------------------------------------------
    def updateDisplay(self):
        """
        Updates the object list view control
        """
        self.pdfOlv.SetColumns([
            ColumnDefn("File", "left", 700, "filename")
            ])
        self.pdfOlv.SetObjects(self.data)
 
########################################################################
class MainFrame(wx.Frame):
    """
    Main frame
    """
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="DoubleClick OLV Demo",
                          size=(800,600))
        panel = UIPanel(self)
        self.createMenu()
        self.Show()
 
    #----------------------------------------------------------------------
    def createMenu(self):
        """
        Create the menus
        """
        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        closeMenuItem = fileMenu.Append(wx.NewId(), "Close",
                                        "Close the application")
        self.Bind(wx.EVT_MENU, self.onClose, closeMenuItem)
 
        menubar.Append(fileMenu, "&File")
        self.SetMenuBar(menubar)
 
    #----------------------------------------------------------------------
    def onClose(self, event):
        """
        Close the application
        """
        self.Close()
 
#----------------------------------------------------------------------
def main():
    """
    Run the application
    """
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    main()

The ObjectListView (OLV) widget works with objects and dictionaries. For this example, we create a File class that we will be feeding to our OLV widget. Then in the panel class, we create the OLV widget along with a browse button. To get the double-click effect, we bind the OLV widget to wx.EVT_LIST_ITEM_ACTIVATED. It’s not a very intuitively named event, but it does work for catching double-clicking. To actually do something with this script, you’ll want to browse to a folder that has PDFs in it. Once you’ve chosen a folder, the getPdfs method is called.

In said method, we use Python’s glob module to look for pdfs. If it returns some, then we update our data list by appending instances of the File class to it. Now you should have something that looks kind of like the screenshot you saw at the beginning of the article. Now when you double-click the item it will print out the path of the PDF and then try to get Window’s cmd.exe path using Python’s os module. Then it will attempt to call Adobe Acrobat Reader’s 32-bit version with a couple of flags and the path to the PDF using Python’s subprocess module. If everyone works correctly, you should see Acrobat load with the PDF you selected.

Note: I’m receiving a wxPyDeprecationWarning when the double click event handler fires. I’m not entirely sure why that is happening as it is talking about key events, but I just thought my readers should know that they can just ignore that as I don’t believe it will affect them in any way.

I tested this code using ObjectListView 1.2, wxPython 2.9.4.0 (classic), and Python 2.7.3 on Windows 7.

Print Friendly