如何在其默认程序中启动文件,然后在脚本完成时关闭它?
问题描述
总结
我有 wxPython GUI,它允许用户打开文件进行查看.目前我使用 os.startfile()
执行此操作.但是,我了解到这不是最好的方法,所以我正在寻求改进.startfile()
的主要缺点是文件一旦启动,我就无法控制它.这意味着用户可以将文件保持打开状态,因此其他用户将无法使用该文件.
我在寻找什么
在我的 GUI 中,可以有子窗口.我通过将 GUI 对象存储在一个列表中来跟踪所有这些,然后当父级关闭时,我只需遍历列表并关闭所有子级.我想对用户选择的任何文件做同样的事情.如何启动一个文件并保留一个 python 对象,以便我可以按命令关闭它?在此先感谢
我的解决方案梦想
- 以这样一种方式启动文件,即有一个我可以在函数之间传递的 Python 对象
- 在默认程序中启动文件并返回 PID 的某种方式
- 一种通过文件名检索 PID 的方法
目前的进展
这是我计划使用的框架.重要的部分是 FileThread
类的 run()
和 end()
函数,因为这是解决方案的所在.
导入 wx从 wx.lib.scrolledpanel 导入 ScrolledPanel导入线程导入操作系统类 GUI(wx.Frame):def __init__(self):wx.Frame.__init__(self, None, -1, '嘿,一个 GUI!', size=(300,300))self.panel = ScrolledPanel(父=self, id=-1)self.panel.SetupScrolling()self.Bind(wx.EVT_CLOSE, self.OnClose)self.openFiles = []self.openBtn = wx.Button(self.panel, -1, "打开文件")self.pollBtn = wx.Button(self.panel, -1, "投票")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, 边框 = 10)self.panel.SetSizer(hbox)self.panel.Layout()def OnOpen(自我,事件):fileName = "AFileIWantToOpenWithTheFullPath.txt"self.openFiles.append(文件线程(文件名))def OnPoll(自我,事件):self.openFiles[0].Poll()def OnClose(自我,事件):对于 self.openFiles 中的文件:文件.end()self.openFiles.remove(文件)自我毁灭()类文件线程(线程.线程):def __init__(自我,文件):threading.Thread.__init__(self)self.file = 文件self.start()定义运行(自我):doc = subprocess.Popen(["start", "/MAX", "/WAIT", self.file], shell=True)返回文件def 民意调查(自我):打印轮询"打印 self.doc.poll()打印 self.doc.pid定义结束(自我):尝试:打印杀死文件{}".format(self.file)除了:print "文件已经被杀死"定义主():应用程序 = wx.PySimpleApp()gui = GUI()gui.Show()app.MainLoop()如果 __name__ == "__main__": main()
一些额外说明
- 我不关心便携性,它只能在办公室周围的几台受控计算机上运行
- 我认为这并不重要,但我正在通过批处理文件运行
pythonw
可执行文件
更新
我用 subprocess.Popen()
玩了一下,但遇到了同样的问题.我可以使用
Popen
对象doc = subprocess.Popen(["start", "Full\Path\to\File.txt"], shell=True)
但是当我poll()
对象时,它总是返回0
.文档说 A None 值表示该进程尚未终止
所以 0
表示我的进程已终止.因此,尝试 kill()
它什么也不做.
我怀疑这是因为当 start
命令完成并启动文件时该过程完成.我想要一些即使在文件启动后也能继续运行的东西,这可以用 Popen()
完成吗?
问题在于,在您的案例中,由 Popen
类处理的进程是 start
shell 命令进程,它在运行与给定文件类型关联的应用程序后立即终止.解决方案是为 start
命令使用 /WAIT
标志,因此 start
进程等待其子进程终止.然后,使用例如 :
shell 参数(默认为 False)指定是否使用 shell 作为程序来执行.如果 shell 为 True,建议将 args 作为字符串而不是序列传递.
并按如下方式运行:
subprocess.Popen("start/WAIT" + self.file, shell=True)
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()
?
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)
相关文章