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)
: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)
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.
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:
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)
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
"""
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):
--- /dev/null
+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.