Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions Doc/library/dialog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,8 @@ listed below:
The below functions when called create a modal, native look-and-feel dialog,
wait for the user's selection, and return it.
The exact return value depends on the function (see below); when the dialog is
cancelled it is an empty string, an empty tuple or ``None``.
The precise type of this empty value may vary between platforms and Tk
versions, so test the result for truth rather than comparing it with a
specific value.
cancelled it is the empty value documented for that function -- an empty
string, an empty tuple, an empty list or ``None``.

.. function:: askopenfile(mode="r", **options)
askopenfiles(mode="r", **options)
Expand All @@ -171,7 +169,7 @@ specific value.
:func:`askopenfile` returns the opened file object, or ``None`` if the
dialog is cancelled.
:func:`askopenfiles` returns a list of the opened file objects, or an empty
tuple if cancelled.
list if cancelled.
The files are opened in mode *mode* (read-only ``'r'`` by default).

.. function:: asksaveasfile(mode="w", **options)
Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_tkinter/test_filedialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ def test_directory(self):
self.check(filedialog.Directory, 'tk_chooseDirectory')


class CancelResultTest(AbstractTkTest, unittest.TestCase):
# On cancellation Tcl may report the empty result as '', () or b''
# (gh-103878). _fixresult() normalizes it to the documented empty value:
# '' for the filename dialogs and () for the multiple-selection dialog.

def check(self, dialog, expected):
for empty in ('', (), b''):
with self.subTest(empty=empty):
result = dialog._fixresult(self.root, empty)
self.assertEqual(result, expected)
self.assertIs(type(result), type(expected))

def test_open(self):
self.check(filedialog.Open(self.root), '')

def test_saveas(self):
self.check(filedialog.SaveAs(self.root), '')

def test_directory(self):
self.check(filedialog.Directory(self.root), '')

def test_openfilenames(self):
self.check(filedialog.Open(self.root, multiple=1), ())

def test_results_preserved(self):
# A real selection is returned unchanged.
single = filedialog.Open(self.root)
self.assertEqual(single._fixresult(self.root, '/a/spam'), '/a/spam')
multiple = filedialog.Open(self.root, multiple=1)
self.assertEqual(multiple._fixresult(self.root, ('/a', '/b')),
('/a', '/b'))


class FileDialogTest(AbstractTkTest, unittest.TestCase):
# The pure-Python FileDialog runs its own modal loop in go(); its logic is
# exercised here without entering the loop.
Expand Down
24 changes: 11 additions & 13 deletions Lib/tkinter/filedialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,9 @@ def _fixoptions(self):
pass

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

def _fixresult(self, widget, result):
if isinstance(result, tuple):
# multiple results:
if self.options.get("multiple"):
# multiple results: a tuple of filenames
if not isinstance(result, tuple):
result = widget.tk.splitlist(result)
result = tuple([getattr(r, "string", r) for r in result])
if result:
path, file = os.path.split(result[0])
self.options["initialdir"] = path
# don't set initialfile or filename, as we have multiple of these
return result
if not widget.tk.wantobjects() and "multiple" in self.options:
# Need to split result explicitly
return self._fixresult(widget, widget.tk.splitlist(result))
return _Dialog._fixresult(self, widget, result)


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

def _fixresult(self, widget, result):
if result:
if not result:
result = '' # normalize the cancelled result (gh-103878)
else:
# convert Tcl path objects to strings
try:
result = result.string
Expand Down Expand Up @@ -420,12 +423,7 @@ def askopenfiles(mode = "r", **options):
"""

files = askopenfilenames(**options)
if files:
ofiles=[]
for filename in files:
ofiles.append(open(filename, mode))
files=ofiles
return files
return [open(filename, mode) for filename in files]


def asksaveasfile(mode = "w", **options):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
The :mod:`tkinter.filedialog` functions that return a filename
(:func:`~tkinter.filedialog.askopenfilename`,
:func:`~tkinter.filedialog.asksaveasfilename` and
:func:`~tkinter.filedialog.askdirectory`) now consistently return an empty
string when the dialog is cancelled, instead of an empty tuple or ``b''``
on some platforms. :func:`~tkinter.filedialog.askopenfilenames` likewise
always returns an empty tuple, and :func:`~tkinter.filedialog.askopenfiles`
an empty list.
Loading