From d165b0a1dc9898cb296c3ffbe1d1abd54fb132a9 Mon Sep 17 00:00:00 2001 From: Tom Charrett Date: Fri, 26 Feb 2021 20:57:51 +0000 Subject: [PATCH 1/7] Fix for issue https://github.com/matplotlib/matplotlib/issues/17769 1) On figure window (FigureFrameWx) close - remove figure manager from from Gcf class previously Gcf.destroy was called with FigureFrameWx not FigureManagerWx instance so figure was not removed. 2) Destroy now done from FigureManagerWx.destroy() method to prevent multiple calls to the close event handler. 3) Remove unneeded wx mainloop yield calls that cause crash on windows form both FigureManagerWx.destroy and FigureFrameWx.Destroy --- lib/matplotlib/backends/backend_wx.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 109cfc155216..c70ca19cdea8 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -971,9 +971,7 @@ def _onClose(self, event): _log.debug("%s - onClose()", type(self)) self.canvas.close_event() self.canvas.stop_event_loop() - Gcf.destroy(self) - if self: - self.Destroy() + Gcf.destroy(self.figmgr) def GetToolBar(self): """Override wxFrame::GetToolBar as we don't have managed toolbar""" @@ -992,9 +990,6 @@ def Destroy(self, *args, **kwargs): super().Destroy(*args, **kwargs) if self.toolbar is not None: self.toolbar.Destroy() - wxapp = wx.GetApp() - if wxapp: - wxapp.Yield() return True @@ -1043,10 +1038,7 @@ def destroy(self, *args): _log.debug("%s - destroy()", type(self)) frame = self.frame if frame: # Else, may have been already deleted, e.g. when closing. - frame.Close() - wxapp = wx.GetApp() - if wxapp: - wxapp.Yield() + frame.Destroy() def full_screen_toggle(self): # docstring inherited From c7c727d2be53b446f0549063fc43609ec7a28174 Mon Sep 17 00:00:00 2001 From: Tom Charrett Date: Sat, 27 Feb 2021 12:52:57 +0000 Subject: [PATCH 2/7] Make plt.close() call thread safe to pass interactive backend tests Prevent multiple attempts to close the frame by setting to FigureManagerWx.frame to None when closed by GUI. When closed by plt.close() call, the frame is closed in a threadsafe way using wx.CallAfter( frame.Close ) --- lib/matplotlib/backends/backend_wx.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index c70ca19cdea8..8e2785569ba5 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -971,7 +971,14 @@ def _onClose(self, event): _log.debug("%s - onClose()", type(self)) self.canvas.close_event() self.canvas.stop_event_loop() - Gcf.destroy(self.figmgr) + # set FigureManagerWx.frame to None to prevent repeated attempts to close + # this frame from with the FigureManagerWx.destroy() + if self.figmgr is not None: + self.figmgr.frame = None + # remove figure manager from Gcf.figs + Gcf.destroy(self.figmgr) + #carry on with wx close event propagation, frame & children destruction + event.Skip() def GetToolBar(self): """Override wxFrame::GetToolBar as we don't have managed toolbar""" @@ -988,8 +995,9 @@ def Destroy(self, *args, **kwargs): # MPLBACKEND=wxagg python -c 'from pylab import *; plot()'. if self and not self.IsBeingDeleted(): super().Destroy(*args, **kwargs) - if self.toolbar is not None: - self.toolbar.Destroy() + # This should not be necessary if the close event is allowed to propagate. + #if self.toolbar is not None: + # self.toolbar.Destroy() return True @@ -1038,7 +1046,9 @@ def destroy(self, *args): _log.debug("%s - destroy()", type(self)) frame = self.frame if frame: # Else, may have been already deleted, e.g. when closing. - frame.Destroy() + # as this can be called from non gui thread from plt.close use + # wx.CallAfter to esnure thread safety. + wx.CallAfter( frame.Close ) def full_screen_toggle(self): # docstring inherited From 329a5f20367941bdcaa896859f8481a98edc16f6 Mon Sep 17 00:00:00 2001 From: Tom Charrett Date: Sat, 27 Feb 2021 13:37:15 +0000 Subject: [PATCH 3/7] Linting fixes --- lib/matplotlib/backends/backend_wx.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 8e2785569ba5..d070742ccc74 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -971,10 +971,10 @@ def _onClose(self, event): _log.debug("%s - onClose()", type(self)) self.canvas.close_event() self.canvas.stop_event_loop() - # set FigureManagerWx.frame to None to prevent repeated attempts to close - # this frame from with the FigureManagerWx.destroy() + # set FigureManagerWx.frame to None to prevent repeated attempts to + # close this frame from with the FigureManagerWx.destroy() if self.figmgr is not None: - self.figmgr.frame = None + self.figmgr.frame = None # remove figure manager from Gcf.figs Gcf.destroy(self.figmgr) #carry on with wx close event propagation, frame & children destruction @@ -995,7 +995,8 @@ def Destroy(self, *args, **kwargs): # MPLBACKEND=wxagg python -c 'from pylab import *; plot()'. if self and not self.IsBeingDeleted(): super().Destroy(*args, **kwargs) - # This should not be necessary if the close event is allowed to propagate. + # This should not be necessary if the close event is allowed to + # propagate. #if self.toolbar is not None: # self.toolbar.Destroy() return True @@ -1046,9 +1047,9 @@ def destroy(self, *args): _log.debug("%s - destroy()", type(self)) frame = self.frame if frame: # Else, may have been already deleted, e.g. when closing. - # as this can be called from non gui thread from plt.close use + # as this can be called from non gui thread from plt.close use # wx.CallAfter to esnure thread safety. - wx.CallAfter( frame.Close ) + wx.CallAfter(frame.Close) def full_screen_toggle(self): # docstring inherited From b5f5118beddcf65f52114fe90037f701529b2e62 Mon Sep 17 00:00:00 2001 From: tohc1 <79719997+tohc1@users.noreply.github.com> Date: Tue, 2 Mar 2021 20:09:24 +0000 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/backends/backend_wx.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index d070742ccc74..efb53fff01a6 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -972,7 +972,7 @@ def _onClose(self, event): self.canvas.close_event() self.canvas.stop_event_loop() # set FigureManagerWx.frame to None to prevent repeated attempts to - # close this frame from with the FigureManagerWx.destroy() + # close this frame from FigureManagerWx.destroy() if self.figmgr is not None: self.figmgr.frame = None # remove figure manager from Gcf.figs @@ -995,10 +995,8 @@ def Destroy(self, *args, **kwargs): # MPLBACKEND=wxagg python -c 'from pylab import *; plot()'. if self and not self.IsBeingDeleted(): super().Destroy(*args, **kwargs) - # This should not be necessary if the close event is allowed to - # propagate. - #if self.toolbar is not None: - # self.toolbar.Destroy() + # self.toolbar.Destroy() should not be necessary if the close event + # is allowed to propagate. return True @@ -1047,8 +1045,8 @@ def destroy(self, *args): _log.debug("%s - destroy()", type(self)) frame = self.frame if frame: # Else, may have been already deleted, e.g. when closing. - # as this can be called from non gui thread from plt.close use - # wx.CallAfter to esnure thread safety. + # As this can be called from non-GUI thread from plt.close use + # wx.CallAfter to ensure thread safety. wx.CallAfter(frame.Close) def full_screen_toggle(self): From b9b40d14af0e99545751d42a01f0ffe80f409cdf Mon Sep 17 00:00:00 2001 From: tohc1 <79719997+tohc1@users.noreply.github.com> Date: Tue, 2 Mar 2021 20:11:10 +0000 Subject: [PATCH 5/7] Remove unneeded check for None --- lib/matplotlib/backends/backend_wx.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index efb53fff01a6..ddd71396264c 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -973,10 +973,9 @@ def _onClose(self, event): self.canvas.stop_event_loop() # set FigureManagerWx.frame to None to prevent repeated attempts to # close this frame from FigureManagerWx.destroy() - if self.figmgr is not None: - self.figmgr.frame = None - # remove figure manager from Gcf.figs - Gcf.destroy(self.figmgr) + self.figmgr.frame = None + # remove figure manager from Gcf.figs + Gcf.destroy(self.figmgr) #carry on with wx close event propagation, frame & children destruction event.Skip() From 42bf18142f29597877835b3ef040e78695f7b2dc Mon Sep 17 00:00:00 2001 From: tohc1 <79719997+tohc1@users.noreply.github.com> Date: Wed, 3 Mar 2021 15:50:06 +0000 Subject: [PATCH 6/7] Update lib/matplotlib/backends/backend_wx.py Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/backends/backend_wx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index ddd71396264c..883912b962ad 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -976,7 +976,7 @@ def _onClose(self, event): self.figmgr.frame = None # remove figure manager from Gcf.figs Gcf.destroy(self.figmgr) - #carry on with wx close event propagation, frame & children destruction + # Carry on with wx close event propagation, frame & children destruction event.Skip() def GetToolBar(self): From 1852052c160f13aece9fb0eca157e5d3da03b36e Mon Sep 17 00:00:00 2001 From: tohc1 <79719997+tohc1@users.noreply.github.com> Date: Wed, 3 Mar 2021 16:03:05 +0000 Subject: [PATCH 7/7] Update backend_wx.py --- lib/matplotlib/backends/backend_wx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 883912b962ad..9d12357d2523 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -976,7 +976,7 @@ def _onClose(self, event): self.figmgr.frame = None # remove figure manager from Gcf.figs Gcf.destroy(self.figmgr) - # Carry on with wx close event propagation, frame & children destruction + # Carry on with close event propagation, frame & children destruction event.Skip() def GetToolBar(self):