Summary
I have wxPython GUI which allows the user to open files to view. Currently I do this with os.startfile()
. However, I've come to learn that this is not the best method, so I'm looking to improve. The main drawback of startfile()
is that I have no control over the file once it has been launched. This means that a user could leave a file open so it would be unavailable for another user.
What I'm Looking For
In my GUI, it is possible to have children windows. I keep track of all of these by storing the GUI objects in a list, then when the parent is closed, I just run through the list and close all the children. I would like to do the same with any file the user selects. How can I launch a file and retain a python object such that I can close it on command? Thanks in advance
My Dreams of a Solution
Progress So Far
Here's the frame I plan on using. The important bits are the run()
and end()
functions of the FileThread
class as this is where the solution will go.
import wx
from wx.lib.scrolledpanel import ScrolledPanel
import threading
import os
class GUI(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Hey, a GUI!', size=(300,300))
self.panel = ScrolledPanel(parent=self, id=-1)
self.panel.SetupScrolling()
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.openFiles = []
self.openBtn = wx.Button(self.panel, -1, "Open a File")
self.pollBtn = wx.Button(self.panel, -1, "Poll")
self.Bind(wx.EVT_BUTTON, self.OnOpen, self.openBtn)
self.Bind(wx.EVT_BUTTON, self.OnPoll, self.pollBtn)
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add((20,20), 1)
vbox.Add(self.openBtn)
vbox.Add((20,20), 1)
vbox.Add(self.pollBtn)
vbox.Add((20,20), 1)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(vbox, flag=wx.TOP|wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.EXPAND, border = 10)
self.panel.SetSizer(hbox)
self.panel.Layout()
def OnOpen(self, event):
fileName = "AFileIWantToOpenWithTheFullPath.txt"
self.openFiles.append(FileThread(fileName))
def OnPoll(self, event):
self.openFiles[0].Poll()
def OnClose(self, event):
for file in self.openFiles:
file.end()
self.openFiles.remove(file)
self.Destroy()
class FileThread(threading.Thread):
def __init__(self, file):
threading.Thread.__init__(self)
self.file = file
self.start()
def run(self):
doc = subprocess.Popen(["start", " /MAX", "/WAIT", self.file], shell=True)
return doc
def Poll(self):
print "polling"
print self.doc.poll()
print self.doc.pid
def end(self):
try:
print "killing file {}".format(self.file)
except:
print "file has already been killed"
def main():
app = wx.PySimpleApp()
gui = GUI()
gui.Show()
app.MainLoop()
if __name__ == "__main__": main()
Some Extra Notes
pythonw
executable through a batch fileUpdate
I played around a bit with subprocess.Popen()
, but ran into the same issue. I can make the Popen
object using
doc = subprocess.Popen(["start", "Full\\Path\\to\\File.txt"], shell=True)
but when I poll()
the object, it always returns 0
. The docs say that A None value indicates that the process hasn’t terminated yet
so the 0
means that my process has terminated. Thus, attempting to kill()
it does nothing.
I suspect this is because the process completes when the start
command finishes and the file is launched. I want something that will keep going even after the file is launched, can this be done with Popen()
?
You can right click on the file to select open with and select the appropriate program for that file to open. Further you can go to control panel, click on default programs. Under "choose the programs that Windows uses by default" you can make the necessary changes to check if that helps.
A default program is the program that Windows uses when you open a particular type of file, such as a music file, an image, or a webpage. on your computer, you can choose one of them to be the default browser. You may be facing the above issue due to unassigned program or application to open that particular file type on the computer.
You can query the registry to identify the default application to open pdf files and then define FileName on your process's StartInfo accordingly. Follow this question for details on doing that: Finding the default application for opening a particular file type on Windows
If you want to change the default program on a Mac, you’ll need a file in the file format you’re looking to open. To start, open the Finder app and find the location of your file.
The problem lies within the fact, that the process being handled by the Popen
class in your case is the start
shell command process, which terminates just after it runs the application associated with given file type. The solution is to use the /WAIT
flag for the start
command, so the start
process waits for its child to terminate. Then, using for example the psutil
module you can achieve what you want with the following sequence:
>>> import psutil
>>> import subprocess
>>> doc = subprocess.Popen(["start", "/WAIT", "file.pdf"], shell=True)
>>> doc.poll()
>>> psutil.Process(doc.pid).get_children()[0].kill()
>>> doc.poll()
0
>>>
After the third line Adobe Reader appears with the file opened. poll
returns None
as long as the window is open thanks to the /WAIT
flag. After killing start
's child Adobe Reader window disappears.
Other probably possible solution would be to find the application associated with given file type in the registry and run it without using start
and shell=True
.
I've tested this on 32 bit Python 2.7.5 on 64 bit Windows Vista, and 32 bit Python 2.7.2 on 64 bit Windows 7. Below is an example run on the latter. I've made some simple adjustments to your code - marked with a freehand red circles (!).
Also, possibly it is worth to consider this comment from the documentation:
The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.
and run the process as follows:
subprocess.Popen("start /WAIT " + self.file, shell=True)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With