Skip to content

Commit 5cf12d8

Browse files
serhiy-storchakachrstphrchvzclaude
committed
gh-103878: Return a consistent empty value from cancelled file dialogs
On cancellation Tcl may report the empty result as '', () or b'' depending on the platform and Tk version. Normalize it so that askopenfilename(), asksaveasfilename() and askdirectory() always return '' and askopenfilenames() always returns (). Open._fixresult() now distinguishes single from multiple results by the 'multiple' option rather than the result type. Co-authored-by: Christopher Chavez <chrischavez@gmx.us> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 109c59e commit 5cf12d8

3 files changed

Lines changed: 50 additions & 7 deletions

File tree

Lib/test/test_tkinter/test_filedialog.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,39 @@ def test_directory(self):
3737
self.check(filedialog.Directory, 'tk_chooseDirectory')
3838

3939

40+
class CancelResultTest(AbstractTkTest, unittest.TestCase):
41+
# On cancellation Tcl may report the empty result as '', () or b''
42+
# (gh-103878). _fixresult() normalizes it to the documented empty value:
43+
# '' for the filename dialogs and () for the multiple-selection dialog.
44+
45+
def check(self, dialog, expected):
46+
for empty in ('', (), b''):
47+
with self.subTest(empty=empty):
48+
result = dialog._fixresult(self.root, empty)
49+
self.assertEqual(result, expected)
50+
self.assertIs(type(result), type(expected))
51+
52+
def test_open(self):
53+
self.check(filedialog.Open(self.root), '')
54+
55+
def test_saveas(self):
56+
self.check(filedialog.SaveAs(self.root), '')
57+
58+
def test_directory(self):
59+
self.check(filedialog.Directory(self.root), '')
60+
61+
def test_openfilenames(self):
62+
self.check(filedialog.Open(self.root, multiple=1), ())
63+
64+
def test_results_preserved(self):
65+
# A real selection is returned unchanged.
66+
single = filedialog.Open(self.root)
67+
self.assertEqual(single._fixresult(self.root, '/a/spam'), '/a/spam')
68+
multiple = filedialog.Open(self.root, multiple=1)
69+
self.assertEqual(multiple._fixresult(self.root, ('/a', '/b')),
70+
('/a', '/b'))
71+
72+
4073
class FileDialogTest(AbstractTkTest, unittest.TestCase):
4174
# The pure-Python FileDialog runs its own modal loop in go(); its logic is
4275
# exercised here without entering the loop.

Lib/tkinter/filedialog.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,9 @@ def _fixoptions(self):
311311
pass
312312

313313
def _fixresult(self, widget, result):
314-
if result:
314+
if not result:
315+
result = '' # normalize the cancelled result (gh-103878)
316+
else:
315317
# keep directory and filename until next time
316318
# convert Tcl path objects to strings
317319
try:
@@ -335,17 +337,16 @@ class Open(_Dialog):
335337
command = "tk_getOpenFile"
336338

337339
def _fixresult(self, widget, result):
338-
if isinstance(result, tuple):
339-
# multiple results:
340+
if self.options.get("multiple"):
341+
# multiple results: a tuple of filenames
342+
if not isinstance(result, tuple):
343+
result = widget.tk.splitlist(result)
340344
result = tuple([getattr(r, "string", r) for r in result])
341345
if result:
342346
path, file = os.path.split(result[0])
343347
self.options["initialdir"] = path
344348
# don't set initialfile or filename, as we have multiple of these
345349
return result
346-
if not widget.tk.wantobjects() and "multiple" in self.options:
347-
# Need to split result explicitly
348-
return self._fixresult(widget, widget.tk.splitlist(result))
349350
return _Dialog._fixresult(self, widget, result)
350351

351352

@@ -362,7 +363,9 @@ class Directory(commondialog.Dialog):
362363
command = "tk_chooseDirectory"
363364

364365
def _fixresult(self, widget, result):
365-
if result:
366+
if not result:
367+
result = '' # normalize the cancelled result (gh-103878)
368+
else:
366369
# convert Tcl path objects to strings
367370
try:
368371
result = result.string
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
The :mod:`tkinter.filedialog` functions that return a filename
2+
(:func:`~tkinter.filedialog.askopenfilename`,
3+
:func:`~tkinter.filedialog.asksaveasfilename` and
4+
:func:`~tkinter.filedialog.askdirectory`) now consistently return an empty
5+
string when the dialog is cancelled, instead of an empty tuple or ``b''``
6+
on some platforms. :func:`~tkinter.filedialog.askopenfilenames` likewise
7+
always returns an empty tuple.

0 commit comments

Comments
 (0)