问题
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
- Launch the file in such a way that there is a Python object which I can pass between functions
- Some way to launch a file in its default program and return the PID
- A way to retrieve the PID with the file name
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
- I'm not concerned with portability, this will only be run on a few controlled computers around the office
- I don't think this matters, but I'm running the
pythonw
executable through a batch file
Update
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()
?
回答1:
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)
来源:https://stackoverflow.com/questions/20777674/how-do-i-launch-a-file-in-its-default-program-and-then-close-it-when-the-script