在Mac OS X上使用Tkinter和Matplotlib重复对话窗口
我刚开始学Tkinter,想用下面的代码通过tkFileDialog.askopenfilename
打开一个文件,然后用Matplotlib画点东西:
import matplotlib.pyplot as plt
import Tkinter, tkFileDialog
root = Tkinter.Tk()
root.withdraw()
file_path = tkFileDialog.askopenfilename()
x = range(10)
plt.plot(x)
plt.show()
运行上面的代码后,我会看到一个对话框,让我选择文件。选择文件后,又会弹出一个对话框让我再选一次文件,同时屏幕底部会出现一个新窗口。我知道问题出在plt.show()
上。发生了什么?怎么才能避免对话框重复出现?我需要为我的任务设置一个Matplotlib的后端吗?
我的版本信息:
Tcl/Tk 8.5.9
Matplotlib 1.3.1
Tkinter $Revision: 81008 $
OS X 10.9.4
我找到两个相关的StackOverflow问题:
pyplot-show-reopens-old-tkinter-dialog 和
matplotlib-figures-not-working-after-tkinter-file-dialog
但没有找到答案。好像root.destroy()
对我没有用。
1 个回答
当我用 python test.py
运行时,下面的代码对我来说似乎是有效的:
import matplotlib.pyplot as plt
import Tkinter, tkFileDialog
root = Tkinter.Tk()
root.withdraw()
file_path = tkFileDialog.askopenfilename()
root.destroy()
print file_path
x = range(10)
plt.plot(x)
plt.show()
我觉得之所以有效,是因为文件对话框的 Tk 实例在 matplotlib 启动自己的实例之前就被销毁了。有趣的是,当我从
ipython --pylab=tk
运行时,我本来以为会出现事件循环启动两次的问题,但它也能正常工作。在这种情况下,标准的解决办法是先检查 Tk 是否已经在运行,然后再启动它(如果需要的话)。
我使用的是 MacOSX 10.7.5,定制构建的 matplotlib(这应该没什么影响)。
我注意到的唯一问题是,在我尝试这个之后,我的 Mac 上的触控板三指手势不再工作了……我正在调查这个问题。
编辑
这是 tkFileDialog.askopenfilename()
执行的 Tk 命令的详细说明:
# breakdown of tkFileDialog.askopenfilename()
import Tkinter as Tk
window = Tk.Tk()
window.withdraw()
w = Tk.Frame(window)
s = w.tk.call('tk_getOpenFile', *w._options({}))
print s
w.destroy()
window.destroy()
当我运行这个(用 python test.py
),我会看到文件打开对话框,可以选择一个文件。点击“确定”后,它会打印出文件名并退出。在我的系统上,这每次都能正常工作。不过,有时在运行这个程序时,我的触控板三指手势会停止工作!而且在程序退出后也不会恢复!!甚至在我关闭运行该程序的终端后也不行!!!
我找到的唯一恢复它们的方法是将以下 matplotlib 代码添加到 test.py
:
import matplotlib
matplotlib.use('tkagg')
import matplotlib.pyplot as plt
plt.figure() # simplified from plt.plot(range(10))
plt.show()
然后点击“Figure 1”的标题栏。这会立刻恢复三指手势。我怀疑这只是 Tkinter
的一个bug。(顺便说一下,我用的是 Tcl/Tk 8.5)
我无法在我的系统上重现文件打开对话框不断重新启动的情况。
如果你启动 test.py
,没有任何 matplotlib 命令的话,能否描述一下你系统上发生了什么?
另外,由于 Tkinter 比较老且明显有bug,我建议使用 Qt 代替?它不仅看起来更好,而且反应更快,我没有遇到任何bug的问题。
编辑 2
我已经分解了 matplotlib 在非交互环境下执行上述命令时的 Tk 操作(也就是用 python test.py
而不是 iPython)。这些是基本的后端调用:
import matplotlib.backends.backend_tkagg as backend
figManager = backend.new_figure_manager(1)
figManager.show()
backend.show.mainloop()
这些调用仍然与后端无关。也就是说,对于 Qt 图形,只需使用:
import matplotlib.backends.backend_qt4agg as backend
如果我们进一步细分到特定后端的层面,我们有:
import matplotlib.backends.backend_tkagg as backend
import Tkinter as Tk
window = Tk.Tk()
window.withdraw()
# uncomment this to use the same Tk instance for tkFileDialog and matplotlib
#import tkFileDialog
#fname = tkFileDialog.askopenfilename(master=window)
#print fname
# figManager = backend.new_figure_manager(1)
from matplotlib.figure import Figure
figure = Figure()
canvas = backend.FigureCanvasTkAgg(figure, master=window)
figManager = backend.FigureManagerTkAgg(canvas, 1, window)
# figManager.show()
window.deiconify()
# backend.show.mainloop()
Tk.mainloop()
首先,运行时将 tkFileDialog 的调用注释掉,检查 matplotlib 图形是否出现并正常工作。然后取消注释 tkFileDialog 的调用,看看是否最终得到了预期的行为。
如果没有,就需要继续分解 FigureCanvasTkAgg
和 FigureManagerTkAgg
来理解发生了什么……
编辑 3
好的,由于问题仍然存在,让我们进一步分解 matplotlib 的 Tk 调用。以下代码完全隔离了我认为必需的所有 matplotlib 的 Tk 操作(所以不再需要从 matplotlib 导入任何东西!)。请注意,我省略了生成工具栏和分配许多回调和按键事件的部分。如果下面的代码现在能正常工作,那么问题就出在这些上。如果不行,我们可以得出结论,这纯粹是 Tk 的问题,很可能是一个应该报告的bug。以下是代码:
import Tkinter as Tk
window = Tk.Tk()
window.withdraw()
# uncomment this to use the same Tk instance for tkFileDialog and matplotlib
#w = Tk.Frame(window)
#fname = w.tk.call('tk_getOpenFile', *w._options({}))
#print fname
#w.destroy()
# canvas = backend.FigureCanvasTkAgg(figure, master=window)
_tkcanvas = Tk.Canvas(master=window, width=640, height=480, borderwidth=4)
_tkphoto = Tk.PhotoImage(master=_tkcanvas, width=640, height=480)
_tkcanvas.create_image(320, 240, image=_tkphoto)
_tkcanvas.focus_set()
# figManager = backend.FigureManagerTkAgg(canvas, 1, window)
window.wm_title("Figure 1")
window.minsize(480, 360)
_tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
# figManager.show()
window.deiconify()
# backend.show.mainloop()
Tk.mainloop()
请尝试注释掉一些行,看看能否让它正常工作。如果不行,我会得出结论这是 Tkinter
的一个bug,应该报告。