and that indeed closes the window. But when trying to show the next figure, I get a stack trace
---------------------------------------------------------------------------
TclError Traceback (most recent call last)
Cell In[5], line 1
----> 1 plt.show()
File ~/git/matplotlib/lib/matplotlib/pyplot.py:526, in show(*args, **kwargs)
482 """
483 Display all open figures.
484
(...)
523 explicitly there.
524 """
525 _warn_if_gui_out_of_main_thread()
--> 526 return _get_backend_mod().show(*args, **kwargs)
File ~/git/matplotlib/lib/matplotlib/backend_bases.py:3398, in _Backend.show(cls, block)
3396 for manager in managers:
3397 try:
-> 3398 manager.show() # Emits a warning for non-interactive backend.
3399 except NonGuiException as exc:
3400 _api.warn_external(str(exc))
File ~/git/matplotlib/lib/matplotlib/backends/_backend_tk.py:561, in FigureManagerTk.show(self)
559 self.canvas.draw_idle()
560 if mpl.rcParams['figure.raise_window']:
--> 561 self.canvas.manager.window.attributes('-topmost', 1)
562 self.canvas.manager.window.attributes('-topmost', 0)
563 self._shown = True
File ~/anaconda3/envs/mpl-dev/lib/python3.11/tkinter/__init__.py:2032, in Wm.wm_attributes(self, *args)
2014 """This subcommand returns or sets platform specific attributes
2015
2016 The first form returns a list of the platform specific flags and
(...)
2029 On Unix, there are currently no special attribute values.
2030 """
2031 args = ('wm', 'attributes', self._w) + args
-> 2032 return self.tk.call(args)
TclError: can't invoke "wm" command: application has been destroye
It seems that Tk is not creating a new application for every plot. But I'm too little of a Tk expert to find out how to do this properly. Ping @daniilS do you know how to handle this?
I think the issue is that just calling first_manager.window.destroy() leaves the manager with a stale reference to the window. Replacing it with Gcf.destroy(first_manager) (which is called when the window is manually closed, see below) should work.
We should try to avoid injecting Gcf into the backend modules if we can and should not assume that the window is managed by pyplot/Gcf.
matplotlib/mpl-gui#8 is an idea we probably have to implement in Matplotlib core (independent of what we do with mpl-gui) in that we need to add the ability to Figure (or CanvasBase) to inject a cleanup callback at initialization time the same way with Artist we inject a remove callback when we add it to the parent (which is also the source of one of our (many) circular references but that is neither here nor there).
In Qt 5, QCoreApplication::quit() was equivalent to calling QCoreApplication::exit(). This just exited the main event loop.
In Qt 6, the method will instead try to close all top-level windows by posting a close event. The windows are free to cancel the shutdown process by ignoring the event.
Call QCoreApplication::exit() to keep the non-conditional behavior.
Perhaps(?) we may be able to simplify #27064 by just always calling exit() instead of quit()? (both also exist for QEventLoop)
I don't remember which Qt version I tested with. But as I read this, quit and exit are equivalent for Qt 5 and don't close windows there. So as long as we support Qt5, we still need to handle window closing ourselves. Furthermore in Qt6 quit() was changed to close the windows. So I suppose the Qt6-only simplification would be to drop window closing, but continue to use quit()?
Activity
QuLogic commented on Jul 4, 2022
It should already do that. If you're seeing alternate behaviour, that's backend specific, so you should specify.
timhoffm commented on Jul 5, 2022
Observed in IPython + Qt
QuLogic commented on Jul 5, 2022
On main or in a release? Qt was implemented in #13306.
daniilS commented on Jul 5, 2022
The figure window stays open in REPL with the tkinter backend too
timhoffm commented on Jul 6, 2022
Tested on main + ipython +
Close all plot windows of a blocking show() on Ctrl+C
Close all plot windows of a blocking show() on Ctrl+C
timhoffm commented on Oct 11, 2023
Note on tkagg: I've tried to add
except KeyboardInterrupt: first_manager.window.destroy()at
matplotlib/lib/matplotlib/backends/_backend_tk.py
Lines 522 to 525 in 6501209
and that indeed closes the window. But when trying to show the next figure, I get a stack trace
--------------------------------------------------------------------------- TclError Traceback (most recent call last) Cell In[5], line 1 ----> 1 plt.show() File ~/git/matplotlib/lib/matplotlib/pyplot.py:526, in show(*args, **kwargs) 482 """ 483 Display all open figures. 484 (...) 523 explicitly there. 524 """ 525 _warn_if_gui_out_of_main_thread() --> 526 return _get_backend_mod().show(*args, **kwargs) File ~/git/matplotlib/lib/matplotlib/backend_bases.py:3398, in _Backend.show(cls, block) 3396 for manager in managers: 3397 try: -> 3398 manager.show() # Emits a warning for non-interactive backend. 3399 except NonGuiException as exc: 3400 _api.warn_external(str(exc)) File ~/git/matplotlib/lib/matplotlib/backends/_backend_tk.py:561, in FigureManagerTk.show(self) 559 self.canvas.draw_idle() 560 if mpl.rcParams['figure.raise_window']: --> 561 self.canvas.manager.window.attributes('-topmost', 1) 562 self.canvas.manager.window.attributes('-topmost', 0) 563 self._shown = True File ~/anaconda3/envs/mpl-dev/lib/python3.11/tkinter/__init__.py:2032, in Wm.wm_attributes(self, *args) 2014 """This subcommand returns or sets platform specific attributes 2015 2016 The first form returns a list of the platform specific flags and (...) 2029 On Unix, there are currently no special attribute values. 2030 """ 2031 args = ('wm', 'attributes', self._w) + args -> 2032 return self.tk.call(args) TclError: can't invoke "wm" command: application has been destroyeIt seems that Tk is not creating a new application for every plot. But I'm too little of a Tk expert to find out how to do this properly. Ping @daniilS do you know how to handle this?
daniilS commented on Oct 12, 2023
I think the issue is that just calling first_manager.window.destroy() leaves the manager with a stale reference to the window. Replacing it with Gcf.destroy(first_manager) (which is called when the window is manually closed, see below) should work.
matplotlib/lib/matplotlib/backends/_backend_tk.py
Lines 551 to 553 in 6501209
Close all plot windows of a blocking show() on Ctrl+C
tacaswell commented on Oct 17, 2023
We should try to avoid injecting Gcf into the backend modules if we can and should not assume that the window is managed by pyplot/Gcf.
matplotlib/mpl-gui#8 is an idea we probably have to implement in Matplotlib core (independent of what we do with mpl-gui) in that we need to add the ability to Figure (or CanvasBase) to inject a cleanup callback at initialization time the same way with Artist we inject a remove callback when we add it to the parent (which is also the source of one of our (many) circular references but that is neither here nor there).
Close all plot windows of a blocking show() on Ctrl+C
anntzer commented on Nov 4, 2023
@timhoffm re:
Were you testing with qt5 or qt6? I just noted https://doc.qt.io/qt-6/qtcore-changes-qt6.html#other-classes:
Perhaps(?) we may be able to simplify #27064 by just always calling exit() instead of quit()? (both also exist for QEventLoop)
timhoffm commented on Nov 5, 2023
I don't remember which Qt version I tested with. But as I read this, quit and exit are equivalent for Qt 5 and don't close windows there. So as long as we support Qt5, we still need to handle window closing ourselves. Furthermore in Qt6 quit() was changed to close the windows. So I suppose the Qt6-only simplification would be to drop window closing, but continue to use quit()?