[Bug]: blitting after closing second tkinter embed causes silent crash
Bug summary
After opening a tkinter popup and embedding a figure a second time and closing it, blitting the figure again causes a silent complete program crash.
Code for reproduction
from tkinter import *
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
mainWindow = Tk()
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
img = ax.imshow(np.random.rand(28,28), cmap="gray", interpolation="None")
fig.canvas.draw()
canvas = FigureCanvasTkAgg(fig, master=mainWindow)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=1)
def openPopup():
popup = Tk()
popupCanvas = FigureCanvasTkAgg(fig, master=popup)
popupCanvas.draw()
popupCanvas.get_tk_widget().grid(row=1, column=1)
popup.protocol("WM_DELETE_WINDOW", popup.destroy)
popup.mainloop()
def doBlit():
img.set_data(np.random.rand(28,28))
ax.draw_artist(img)
fig.canvas.blit(ax.bbox) # this blit line is the cause of the crash
fig.canvas.flush_events()
button_1 = Button(mainWindow,command=(openPopup),text="open popup")
button_1.grid(row=1,column=2)
button_2 = Button(mainWindow,command=(doBlit),text="do blitting")
button_2.grid(row=1,column=3)
mainWindow.mainloop()
Actual outcome
Program crashes with no error message.
Expected outcome
Either the program should crash with an error message, or the blit should succeed and the new image should be rendered.
Additional information
I think this is probably related to #16580, but the lack of any error message is definitely different. In the larger program where I'm trying this blitting doesn't actively crash in this way with a separate figure that has 2 scatterplots and a title which are being blitted and embedded in the same manner, but the other figure which is just an image is causing the program to crash. If anyone knows a way to fix this in the meantime I would be very grateful.
Operating system
Windows
Matplotlib Version
3.4.3
Matplotlib Backend
Agg
Python version
3.8.1
Jupyter version
6.4.6
Installation
pip
Do either of the fixes I suggested in #16580 help?
The first fix stops the complete crash, but the blitting still fails with the following error message(s).
Traceback (most recent call last):
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 117, in blit
photoimage.tk.call(_blit_tcl_name, argsid)
_tkinter.TclError: invalid command name "pyimage4"
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\tkinter\__init
__.py", line 1883, in __call__
return self.func(*args)
File "errorExample.py", line 32, in doBlit
fig.canvas.blit(ax.bbox)
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\backend_tkagg.py", line 13, in blit
_backend_tk.blit(self._tkphoto, self.renderer.buffer_rgba(),
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 122, in blit
photoimage.tk.call(_blit_tcl_name, argsid)
_tkinter.TclError
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 117, in blit
photoimage.tk.call(_blit_tcl_name, argsid)
_tkinter.TclError: invalid command name "pyimage4"
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\tkinter\__init
__.py", line 1883, in __call__
return self.func(*args)
File "errorExample.py", line 27, in openPopup
popup.mainloop()
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\tkinter\__init
__.py", line 1420, in mainloop
self.tk.mainloop(n)
File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 63, in _blit
photoimage, dataptr, offsets, bboxptr, blank = _blit_args.pop(argsid)
KeyError: '1510834383744'
The second fix still crashes completely silently. The fact that the scatterplot blitting isn't having any issues in my circumstances but blitting the image does have issues seems strange to me. Also something I just noticed, if I open the popup and press the blit button only the popup blits, the main window stays completely static. Is there some way to force the blitting to stay locked onto the main window and not switch to the popup?
It crashes because you use the same figure for the two windows. When you close your popup, you need to change the canvas of the figure so it refers to the main window again. Also, try not to use pyplot with tkinter, instead use Figure.
from tkinter import *
import numpy as np
import matplotlib
from matplotlib.figure import Figure
matplotlib.use("Agg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
mainWindow = Tk()
fig = Figure()
ax = fig.add_subplot(1,1,1)
img = ax.imshow(np.random.rand(28,28), cmap="gray", interpolation="None")
fig.canvas.draw()
canvas = FigureCanvasTkAgg(fig, master=mainWindow)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=1)
def close_popup(popup):
fig.set_canvas(canvas)
canvas.draw() # This is necessary, no idea why
popup.quit()
popup.destroy()
def openPopup():
popup = Toplevel()
popupCanvas = FigureCanvasTkAgg(fig, master=popup)
popupCanvas.draw()
popupCanvas.get_tk_widget().grid(row=1, column=1)
popup.protocol("WM_DELETE_WINDOW", lambda p=popup: close_popup(popup))
popup.mainloop()
def doBlit():
img.set_data(np.random.rand(28,28))
ax.draw_artist(img)
fig.canvas.blit(fig.bbox) # this blit line is the cause of the crash
fig.canvas.flush_events()
button_1 = Button(mainWindow,command=(openPopup),text="open popup")
button_1.grid(row=1,column=2)
button_2 = Button(mainWindow,command=(doBlit),text="do blitting")
button_2.grid(row=1,column=3)
mainWindow.mainloop()