Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0bb70bf

Browse files
tacaswellQuLogic
authored andcommitted
Backport PR matplotlib#17391: tk/wx: Fix saving after the window is closed
1 parent 391bb24 commit 0bb70bf

File tree

3 files changed

+41
-16
lines changed

3 files changed

+41
-16
lines changed

lib/matplotlib/backends/_backend_tk.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,12 @@ def release(self, event):
538538

539539
def set_cursor(self, cursor):
540540
window = self.canvas.get_tk_widget().master
541-
window.configure(cursor=cursord[cursor])
542-
window.update_idletasks()
541+
try:
542+
window.configure(cursor=cursord[cursor])
543+
except tkinter.TclError:
544+
pass
545+
else:
546+
window.update_idletasks()
543547

544548
def _Button(self, text, file, command, extension='.gif'):
545549
img_file = str(cbook._get_data_path('images', file + extension))

lib/matplotlib/backends/backend_wx.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,9 @@ def gui_repaint(self, drawDC=None, origin='WX'):
702702
The 'WXAgg' backend sets origin accordingly.
703703
"""
704704
DEBUG_MSG("gui_repaint()", 1, self)
705-
if self.IsShownOnScreen():
705+
# The "if self" check avoids a "wrapped C/C++ object has been deleted"
706+
# RuntimeError if doing things after window is closed.
707+
if self and self.IsShownOnScreen():
706708
if not drawDC:
707709
# not called from OnPaint use a ClientDC
708710
drawDC = wx.ClientDC(self)
@@ -978,14 +980,11 @@ def _print_image(self, filename, filetype, *args, **kwargs):
978980

979981
# Now that we have rendered into the bitmap, save it to the appropriate
980982
# file type and clean up.
981-
if isinstance(filename, str):
982-
if not image.SaveFile(filename, filetype):
983-
raise RuntimeError(f'Could not save figure to {filename}')
984-
elif cbook.is_writable_file_like(filename):
985-
if not isinstance(image, wx.Image):
986-
image = image.ConvertToImage()
987-
if not image.SaveStream(filename, filetype):
988-
raise RuntimeError(f'Could not save figure to {filename}')
983+
if (cbook.is_writable_file_like(filename) and
984+
not isinstance(image, wx.Image)):
985+
image = image.ConvertToImage()
986+
if not image.SaveFile(filename, filetype):
987+
raise RuntimeError(f'Could not save figure to {filename}')
989988

990989
# Restore everything to normal
991990
self.bitmap = origBitmap
@@ -997,7 +996,10 @@ def _print_image(self, filename, filetype, *args, **kwargs):
997996
# otherwise.
998997
if self._isDrawn:
999998
self.draw()
1000-
self.Refresh()
999+
# The "if self" check avoids a "wrapped C/C++ object has been deleted"
1000+
# RuntimeError if doing things after window is closed.
1001+
if self:
1002+
self.Refresh()
10011003

10021004

10031005
########################################################################

lib/matplotlib/tests/test_backends_interactive.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def _get_testable_interactive_backends():
5252
_test_script = """\
5353
import importlib
5454
import importlib.util
55+
import io
5556
import sys
5657
from unittest import TestCase
5758
@@ -107,17 +108,34 @@ def check_alt_backend(alt_backend):
107108
# Trigger quitting upon draw.
108109
fig.canvas.mpl_connect("draw_event", lambda event: timer.start())
109110
111+
result = io.BytesIO()
112+
fig.savefig(result, format='png')
113+
110114
plt.show()
115+
116+
# Ensure that the window is really closed.
117+
plt.pause(0.5)
118+
119+
# Test that saving works after interactive window is closed, but the figure is
120+
# not deleted.
121+
result_after = io.BytesIO()
122+
fig.savefig(result_after, format='png')
123+
124+
if not backend.startswith('qt5') and sys.platform == 'darwin':
125+
# FIXME: This should be enabled everywhere once Qt5 is fixed on macOS to
126+
# not resize incorrectly.
127+
assert_equal(result.getvalue(), result_after.getvalue())
111128
"""
112129
_test_timeout = 10 # Empirically, 1s is not enough on Travis.
113130

114131

115132
@pytest.mark.parametrize("backend", _get_testable_interactive_backends())
116133
@pytest.mark.flaky(reruns=3)
117134
def test_interactive_backend(backend):
118-
proc = subprocess.run([sys.executable, "-c", _test_script],
119-
env={**os.environ, "MPLBACKEND": backend},
120-
timeout=_test_timeout)
135+
proc = subprocess.run(
136+
[sys.executable, "-c", _test_script],
137+
env={**os.environ, "MPLBACKEND": backend, "SOURCE_DATE_EPOCH": "0"},
138+
timeout=_test_timeout)
121139
if proc.returncode:
122140
pytest.fail("The subprocess returned with non-zero exit status "
123141
f"{proc.returncode}.")
@@ -129,7 +147,8 @@ def test_interactive_backend(backend):
129147
def test_webagg():
130148
pytest.importorskip("tornado")
131149
proc = subprocess.Popen([sys.executable, "-c", _test_script],
132-
env={**os.environ, "MPLBACKEND": "webagg"})
150+
env={**os.environ, "MPLBACKEND": "webagg",
151+
"SOURCE_DATE_EPOCH": "0"})
133152
url = "http://{}:{}".format(
134153
mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"])
135154
timeout = time.perf_counter() + _test_timeout

0 commit comments

Comments
 (0)