3

I'm newbie in Tkinter. I try to use next code to open a file using tkFileDialog.askopenfilename and then plot something with 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()

After running the above script I get dialog window to open my file. After file selection I get repeated dialog window to open the file and a new window at the bottom of my screen. I know that the problem is because of plt.show(). What happens and how to avoid dialog window reopening? Should I set a Matplotlib backend for my task?

My versions:

Tcl/Tk 8.5.9

Matplotlib 1.3.1

Tkinter $Revision: 81008 $

OS X 10.9.4

I have found two related stackoverflow questions: pyplot-show-reopens-old-tkinter-dialog and matplotlib-figures-not-working-after-tkinter-file-dialog but no answers. It seems that root.destroy() is not working for me.

Community
  • 1
  • 1
drastega
  • 1,581
  • 5
  • 30
  • 42
  • I have updated my answer with a breakdown of the Tk commands executed by `tkFileDialog.askopenfilename()`. Could you try this out and describe the behavior on your system? – Stefan Jul 16 '14 at 14:31
  • 1
    I have broken down matplotlib's Tk calls to the absolute minimum that is necessary to display the figure. Could you check if the code in **Edit 2** finally shows the expected behavior? – Stefan Jul 17 '14 at 09:47
  • Please check **Edit 3**, which now contains all of matplotlib's Tk actions explicitly and is now completely standalone from matplotlib. If the problem persists, we can conclude that it is a bug in `Tkinter`, which should be reported. – Stefan Jul 20 '14 at 08:45
  • I've played with Edit 3 and the problem still persists. I also think this is Tkinter problem. Thank you very much @Stefan! – drastega Jul 20 '14 at 17:02

1 Answers1

5

When run with python test.py, the following seems to work for me:

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()

I think it works because the Tk instance for the file dialog is destroyed before matplotlib fires up its own. Interestingly, it also works for me when run from

ipython --pylab=tk

where I would have expected a problem with starting the event loop twice. The canonical solution in this case would be to check if Tk is already running before firing it up (again).

I'm on MacOSX 10.7.5, custom built matplotlib (shouldn't matter).

The only thing I noticed was that after experimenting with this, the touchpad swipe gestures on my Mac no longer work... Looking into this.

Edit

Here is a breakdown of the Tk commands executed by tkFileDialog.askopenfilename():

# 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()

When I run this (with python test.py), I get the file open dialog, where I can choose a file. Upon "OK" it prints the file name and exits. This works every time on my system. However, sometimes the 3-finger gestures on my touchpad stop working while running this program! And they don't come back after the program exits!! Not even after I kill the terminal the program was running in!!!

The only way I found to bring them back is to add the following matplotlib code to test.py:

import matplotlib
matplotlib.use('tkagg')
import matplotlib.pyplot as plt
plt.figure()     # simplified from plt.plot(range(10))
plt.show()

and then click on the title bar of "Figure 1". This instantly brings back the 3-finger gestures. I suspect this is simply a bug in Tkinter. (I'm on Tcl/Tk 8.5, by the way)

I cannot reproduce the behavior that the file open dialog is constantly relaunched on my system.

Could you please describe what happens on your system if you launch test.py, without any matplotlib commands?

Alternatively, since Tkinter is old and apparently buggy, may I suggest to use Qt instead? Not only does it look much nicer, it is also snappier and I didn't have any problems with bugs.

Edit 2

I have broken down the Tk actions that matplotlib takes when executing the above commands in a non-interactive environment (i.e. with python test.py and not from iPython). These are the essential backend calls:

import matplotlib.backends.backend_tkagg as backend
figManager = backend.new_figure_manager(1)
figManager.show()
backend.show.mainloop()

These are still backend independent. I.e., for a Qt figure, simply use:

import matplotlib.backends.backend_qt4agg as backend

If we break this down further to the backend-specific layer, we have:

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()

First, run with the tkFileDialog calls commented out and check if the matplotlib figure appears and behaves correctly. Then uncomment the tkFileDialog calls and see if you finally get the expected behavior.

If not, one has to continue breaking down FigureCanvasTkAgg and FigureManagerTkAgg to understand what is going on...

Edit 3

OK, since the problem persisted, let's break down matplotlib's Tk calls even further. The following code completely isolates all of matplotlib's Tk actions that I consider essential (so it is no longer necessary to import anything from matplotlib!). Note that I left out generating the toolbar and assigning lots of callbacks and keypress events. If the code below works now, then the problem lies with these. If it doesn't work, we can conclude that it is purely a Tk problem, and most likely a bug that should be reported. Here is the code:

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()

Please play around with commenting out some lines and see if you can get it working correctly. If not, I would conclude that this is a bug in Tkinter, which should be reported.

Stefan
  • 4,380
  • 2
  • 30
  • 33
  • Thanks @Stefan but `python test.py` is not working for me. I face the same problems as before. By the way, I forgot to mention about problems with my touchpad. – drastega Jul 15 '14 at 16:51
  • When I launch `test.py` without any matplotlib commands everything is working without problems. Adding matplotlib commands still cause appearance of second dialog window strange touchpad behavior. Yes, I've already started to examine Qt. Thanks a lot @Stefan! – drastega Jul 16 '14 at 15:06
  • Sorry for late answer @Stefan! I was very busy. Everything is working good when Tk block is commented. When I uncomment it I get dialog window and repeated dialog window again. But now is much better because I have no problems with my touchpad. – drastega Jul 18 '14 at 10:36
  • I have the same problem with Tkinter breaking my Mac's gestures. Running `killall Dock` in Terminal.app fixes it, but this is kind of a pain in the ass. –  Aug 14 '15 at 00:16