mirror of
https://github.com/python/cpython.git
synced 2026-08-01 09:31:29 +08:00
Opening several files at once is error-prone, and the returned list cannot be used in a "with" statement. Iterate over the names returned by askopenfilenames() and open them one by one instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
644 lines
23 KiB
Python
644 lines
23 KiB
Python
"""File selection dialog classes.
|
|
|
|
Classes:
|
|
|
|
- FileDialog
|
|
- LoadFileDialog
|
|
- SaveFileDialog
|
|
|
|
This module also presents tk common file dialogues, it provides interfaces
|
|
to the native file dialogues available in Tk 4.2 and newer, and the
|
|
directory dialogue available in Tk 8.3 and newer.
|
|
These interfaces were written by Fredrik Lundh, May 1997.
|
|
"""
|
|
__all__ = ["FileDialog", "LoadFileDialog", "SaveFileDialog",
|
|
"Open", "SaveAs", "Directory",
|
|
"askopenfilename", "asksaveasfilename", "askopenfilenames",
|
|
"askopenfile", "askopenfiles", "asksaveasfile", "askdirectory"]
|
|
|
|
import fnmatch
|
|
import os
|
|
import tkinter
|
|
from tkinter import (
|
|
ACTIVE, CENTER, EW, NORMAL, NS, NSEW, RAISED, W, YES, BOTTOM, TOP, Tk, X,
|
|
Toplevel, END, Listbox, BOTH,
|
|
)
|
|
from tkinter import ttk
|
|
from tkinter import messagebox
|
|
from tkinter import commondialog
|
|
from tkinter.simpledialog import (_setup_dialog, _place_window, _temp_grab_focus,
|
|
_underline_ampersand, _find_alt_key_target)
|
|
|
|
|
|
dialogstates = {}
|
|
|
|
|
|
class FileDialog:
|
|
|
|
"""Standard file selection dialog -- no checks on selected file.
|
|
|
|
The layout and behavior follow the classic Motif file selection dialog.
|
|
|
|
Usage:
|
|
|
|
d = FileDialog(master)
|
|
fname = d.go(dir_or_file, pattern, default, key)
|
|
if fname is None: ...canceled...
|
|
else: ...open file...
|
|
|
|
All arguments to go() are optional.
|
|
|
|
The 'key' argument specifies a key in the global dictionary
|
|
'dialogstates', which keeps track of the values for the directory
|
|
and pattern arguments, overriding the values passed in (it does
|
|
not keep track of the default argument!). If no key is specified,
|
|
the dialog keeps no memory of previous state. Note that memory is
|
|
kept even when the dialog is canceled. (All this emulates the
|
|
behavior of the Macintosh file selection dialogs.)
|
|
|
|
"""
|
|
|
|
title = "File Selection Dialog"
|
|
|
|
def _widget(self, klass, master, **kw):
|
|
# Create a themed (ttk) or classic (tkinter) widget. ttk has no
|
|
# Listbox, so the directory and file lists stay classic.
|
|
return getattr(ttk if self.use_ttk else tkinter, klass)(master, **kw)
|
|
|
|
def _frame(self, master):
|
|
# A structural frame. The classic file dialog gives its frames a
|
|
# raised border on X11; the themed one does not (cf. tk_dialog).
|
|
frame = self._widget('Frame', master)
|
|
if not self.use_ttk and self.top._windowingsystem == 'x11':
|
|
frame.configure(relief=RAISED, bd=1)
|
|
return frame
|
|
|
|
def _make_list(self, column, label, browse, activate):
|
|
# Create a labelled listbox with vertical and horizontal scrollbars in
|
|
# the middle frame, like the Motif file dialog (cf. xmfbox.tcl
|
|
# MotifFDialog_MakeSList).
|
|
frame = self._widget('Frame', self.midframe)
|
|
frame.grid(row=0, column=column, sticky=NSEW, padx='3p', pady='3p')
|
|
frame.grid_rowconfigure(1, weight=1)
|
|
frame.grid_columnconfigure(0, weight=1)
|
|
vbar = self._widget('Scrollbar', frame, takefocus=0)
|
|
hbar = self._widget('Scrollbar', frame, orient='horizontal', takefocus=0)
|
|
listbox = Listbox(frame, exportselection=0,
|
|
xscrollcommand=(hbar, 'set'),
|
|
yscrollcommand=(vbar, 'set'))
|
|
vbar.configure(command=(listbox, 'yview'))
|
|
hbar.configure(command=(listbox, 'xview'))
|
|
self._label(frame, label, listbox).grid(
|
|
row=0, column=0, columnspan=2, sticky=EW, padx='1.5p', pady='1.5p')
|
|
listbox.grid(row=1, column=0, sticky=NSEW)
|
|
vbar.grid(row=1, column=1, sticky=NS)
|
|
hbar.grid(row=2, column=0, sticky=EW)
|
|
# Instance bindings fire after the Listbox class bindings, which have
|
|
# already updated the selection.
|
|
btags = listbox.bindtags()
|
|
listbox.bindtags(btags[1:] + btags[:1])
|
|
listbox.bind('<<ListboxSelect>>', browse)
|
|
listbox.bind('<Double-ButtonRelease-1>', activate)
|
|
listbox.bind('<Return>', lambda e: (browse(e), activate(e)))
|
|
# Type a few characters to jump to a matching entry, like the Motif
|
|
# file dialog (cf. xmfbox.tcl ListBoxKeyAccel).
|
|
listbox.bind('<Key>', self._listbox_keyaccel)
|
|
return listbox, vbar, hbar
|
|
|
|
def __init__(self, master, title=None, *, use_ttk=True):
|
|
if title is None: title = self.title
|
|
self.master = master
|
|
self.use_ttk = use_ttk
|
|
self.directory = None
|
|
self._keyaccel = {} # listbox -> recently typed prefix
|
|
self._keyaccel_after = {} # listbox -> pending reset callback id
|
|
|
|
self.top = Toplevel(master)
|
|
self.top.withdraw() # remain invisible until placed by go()
|
|
self.top.title(title)
|
|
self.top.iconname(title)
|
|
# Keep the dialog above its parent, like SimpleDialog and Dialog (cf.
|
|
# xmfbox.tcl). Skip it when the master is not viewable, or the dialog
|
|
# would itself be opened withdrawn.
|
|
if master.winfo_viewable():
|
|
self.top.transient(master)
|
|
_setup_dialog(self.top)
|
|
if self.use_ttk:
|
|
# Use a single themed background for the whole dialog so it blends
|
|
# with the ttk widgets (cf. tk::MessageBox).
|
|
self.top.configure(
|
|
background=ttk.Style(self.top).lookup('.', 'background'))
|
|
|
|
# Tk traverses focus in widget-creation order, so the widgets are
|
|
# created top to bottom -- filter, lists, selection, buttons -- to make
|
|
# Tab follow the visual layout, like the Motif file dialog (cf.
|
|
# xmfbox.tcl).
|
|
self.filter = self._widget('Entry', self.top)
|
|
self.filter.bind('<Return>', self.filter_command)
|
|
self.filter_label = self._label(self.top, 'Fil&ter:', self.filter)
|
|
self.filter_label.pack(side=TOP, fill=X, padx='4.5p', pady='3p')
|
|
self.filter.pack(side=TOP, fill=X, padx='3p')
|
|
|
|
self.midframe = self._frame(self.top)
|
|
self.midframe.pack(side=TOP, expand=YES, fill=BOTH)
|
|
|
|
# Directory list (left) and file list (right), each with a label and
|
|
# vertical and horizontal scrollbars, like the Motif file dialog (cf.
|
|
# xmfbox.tcl).
|
|
self.dirs, self.dirsbar, self.dirshbar = self._make_list(
|
|
0, '&Directory:', self.dirs_select_event, self.dirs_double_event)
|
|
self.files, self.filesbar, self.fileshbar = self._make_list(
|
|
1, 'Fi&les:', self.files_select_event, self.files_double_event)
|
|
|
|
# Give the file list twice the width of the directory list, like the
|
|
# Motif file dialog (cf. xmfbox.tcl).
|
|
self.midframe.grid_rowconfigure(0, weight=1)
|
|
self.midframe.grid_columnconfigure(0, weight=1)
|
|
self.midframe.grid_columnconfigure(1, minsize=150, weight=2)
|
|
|
|
self.selection = self._widget('Entry', self.top)
|
|
self.selection.bind('<Return>', self.ok_event)
|
|
self.selection_label = self._label(self.top, '&Selection:', self.selection)
|
|
self.selection.pack(side=BOTTOM, fill=X, padx='3p', pady='3p')
|
|
self.selection_label.pack(side=BOTTOM, fill=X, padx='4.5p')
|
|
|
|
# Created last so the buttons traverse last, but packed below the
|
|
# selection field.
|
|
self.botframe = self._frame(self.top)
|
|
self.botframe.pack(side=BOTTOM, fill=X, before=self.selection)
|
|
|
|
# tk::MessageBox and tk_dialog space the buttons differently.
|
|
padx, pady = ('3m', '2m') if self.use_ttk else ('7.5p', '3p')
|
|
for i, (name, label, command) in enumerate((
|
|
('ok', '&OK', self.ok_command),
|
|
('filter', '&Filter', self.filter_command),
|
|
('cancel', '&Cancel', self.cancel_command))):
|
|
# Create a button with an accelerator key marked by "&" in the text,
|
|
# like SimpleDialog and Dialog (cf. tk::AmpWidget).
|
|
text, underline = _underline_ampersand(label)
|
|
button = self._widget('Button', self.botframe, name=name, text=text,
|
|
underline=underline, command=command,
|
|
default=ACTIVE if name == 'ok' else NORMAL)
|
|
button.bind('<<AltUnderlined>>', lambda e: e.widget.invoke())
|
|
button.grid(column=i, row=0, sticky=EW, padx=padx, pady=pady)
|
|
# tk::MessageBox makes the buttons equal width; tk_dialog does not.
|
|
self.botframe.grid_columnconfigure(
|
|
i, uniform='buttons' if self.use_ttk else '')
|
|
if self.top._windowingsystem == 'aqua':
|
|
self.botframe.grid_columnconfigure(i, minsize=90)
|
|
button.grid_configure(pady=7)
|
|
setattr(self, name + '_button', button)
|
|
self.botframe.grid_anchor(CENTER)
|
|
|
|
self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
|
|
# Alt + an underlined character invokes the matching button (cf.
|
|
# ::tk::AltKeyInDialog), like SimpleDialog and Dialog.
|
|
self.top.bind('<Alt-Key>', self._alt_key)
|
|
# The default ring follows the keyboard focus among the buttons (cf.
|
|
# tk::MessageBox), like SimpleDialog and Dialog.
|
|
self.top.bind('<FocusIn>', lambda e: self._set_default(e.widget, ACTIVE))
|
|
self.top.bind('<FocusOut>', lambda e: self._set_default(e.widget, NORMAL))
|
|
self.top.bind('<Escape>', self.cancel_command)
|
|
|
|
def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None):
|
|
if key and key in dialogstates:
|
|
self.directory, pattern = dialogstates[key]
|
|
else:
|
|
dir_or_file = os.path.expanduser(dir_or_file)
|
|
if os.path.isdir(dir_or_file):
|
|
self.directory = dir_or_file
|
|
else:
|
|
self.directory, default = os.path.split(dir_or_file)
|
|
self.set_filter(self.directory, pattern)
|
|
self.set_selection(default)
|
|
self.filter_command()
|
|
# Center the dialog over its parent and make it visible, like
|
|
# SimpleDialog and Dialog (cf. xmfbox.tcl).
|
|
_place_window(self.top, self.master)
|
|
self.selection.select_range(0, END) # so the user can type a new name
|
|
self.top.wait_visibility() # window needs to be visible for the grab
|
|
self.how = None
|
|
# Grab the input and restore the previous focus and grab afterwards,
|
|
# like SimpleDialog and Dialog. go() destroys the window itself below,
|
|
# so leave it in place here.
|
|
with _temp_grab_focus(self.top, self.selection, destroy=False):
|
|
self.master.mainloop() # Exited by self.quit(how)
|
|
if key:
|
|
directory, pattern = self.get_filter()
|
|
if self.how:
|
|
directory = os.path.dirname(self.how)
|
|
dialogstates[key] = directory, pattern
|
|
self.top.destroy()
|
|
return self.how
|
|
|
|
def quit(self, how=None):
|
|
self.how = how
|
|
self.master.quit() # Exit mainloop()
|
|
|
|
def _alt_key(self, event):
|
|
# Invoke the button whose accelerator matches the Alt key (cf.
|
|
# SimpleDialog and Dialog).
|
|
target = _find_alt_key_target(self.top, event.char)
|
|
if target is not None:
|
|
target.event_generate('<<AltUnderlined>>')
|
|
|
|
def _set_default(self, widget, state):
|
|
# Set a button's default ring.
|
|
if widget.winfo_class() in ('Button', 'TButton'):
|
|
widget.configure(default=state)
|
|
|
|
def _label(self, master, label, target):
|
|
# Create a field label whose "&" accelerator focuses the target widget,
|
|
# like the labels of the Motif file dialog (cf. xmfbox.tcl).
|
|
text, underline = _underline_ampersand(label)
|
|
widget = self._widget('Label', master, text=text, underline=underline,
|
|
anchor=W)
|
|
widget.bind('<<AltUnderlined>>', lambda e: target.focus_set())
|
|
return widget
|
|
|
|
def _listbox_keyaccel(self, event):
|
|
# Append the typed character and jump to a matching entry, resetting
|
|
# after a short pause (cf. xmfbox.tcl ListBoxKeyAccel_Key).
|
|
char = event.char
|
|
if not char or not char.isprintable():
|
|
return
|
|
listbox = event.widget
|
|
prefix = self._keyaccel.get(listbox, '') + char
|
|
self._keyaccel[listbox] = prefix
|
|
self._listbox_keyaccel_goto(listbox, prefix)
|
|
after_id = self._keyaccel_after.get(listbox)
|
|
if after_id is not None:
|
|
listbox.after_cancel(after_id)
|
|
self._keyaccel_after[listbox] = listbox.after(
|
|
500, lambda: self._keyaccel.pop(listbox, None))
|
|
|
|
def _listbox_keyaccel_goto(self, listbox, prefix):
|
|
# Select the first entry not preceding the typed prefix (cf. xmfbox.tcl
|
|
# ListBoxKeyAccel_Goto).
|
|
prefix = prefix.lower()
|
|
index = -1
|
|
for i in range(listbox.size()):
|
|
item = listbox.get(i).lower()
|
|
if prefix >= item:
|
|
index = i
|
|
if prefix <= item:
|
|
index = i
|
|
break
|
|
if index >= 0:
|
|
listbox.selection_clear(0, END)
|
|
listbox.selection_set(index)
|
|
listbox.activate(index)
|
|
listbox.see(index)
|
|
listbox.event_generate('<<ListboxSelect>>')
|
|
|
|
def dirs_double_event(self, event):
|
|
self.filter_command()
|
|
# Highlight an entry so the directory list stays keyboard-navigable
|
|
# after entering a directory, like the Motif file dialog (cf.
|
|
# xmfbox.tcl); prefer the first subdirectory over the ".." entry.
|
|
index = 1 if self.dirs.size() > 1 else 0
|
|
self.dirs.selection_set(index)
|
|
self.dirs.activate(index)
|
|
|
|
def dirs_select_event(self, event):
|
|
# Show a selection in only one list at a time (cf. xmfbox.tcl).
|
|
self.files.selection_clear(0, END)
|
|
dir, pat = self.get_filter()
|
|
subdir = self.dirs.get('active')
|
|
dir = os.path.normpath(os.path.join(self.directory, subdir))
|
|
self.set_filter(dir, pat)
|
|
# Show the end of a long path, like the Motif file dialog (cf.
|
|
# xmfbox.tcl).
|
|
self.filter.xview(END)
|
|
|
|
def files_double_event(self, event):
|
|
self.ok_command()
|
|
|
|
def files_select_event(self, event):
|
|
# Show a selection in only one list at a time (cf. xmfbox.tcl).
|
|
self.dirs.selection_clear(0, END)
|
|
file = self.files.get('active')
|
|
self.set_selection(file)
|
|
self.selection.xview(END)
|
|
|
|
def ok_event(self, event):
|
|
self.ok_command()
|
|
|
|
def ok_command(self):
|
|
self.quit(self.get_selection())
|
|
|
|
def filter_command(self, event=None):
|
|
dir, pat = self.get_filter()
|
|
try:
|
|
names = os.listdir(dir)
|
|
except OSError:
|
|
self.master.bell()
|
|
return
|
|
self.directory = dir
|
|
self.set_filter(dir, pat)
|
|
names.sort()
|
|
subdirs = [os.pardir]
|
|
matchingfiles = []
|
|
for name in names:
|
|
fullname = os.path.join(dir, name)
|
|
if os.path.isdir(fullname):
|
|
subdirs.append(name)
|
|
elif fnmatch.fnmatch(name, pat):
|
|
matchingfiles.append(name)
|
|
self.dirs.delete(0, END)
|
|
for name in subdirs:
|
|
self.dirs.insert(END, name)
|
|
self.files.delete(0, END)
|
|
for name in matchingfiles:
|
|
self.files.insert(END, name)
|
|
head, tail = os.path.split(self.get_selection())
|
|
if tail == os.curdir: tail = ''
|
|
self.set_selection(tail)
|
|
|
|
def get_filter(self):
|
|
filter = self.filter.get()
|
|
filter = os.path.expanduser(filter)
|
|
if filter[-1:] == os.sep or os.path.isdir(filter):
|
|
filter = os.path.join(filter, "*")
|
|
return os.path.split(filter)
|
|
|
|
def get_selection(self):
|
|
file = self.selection.get()
|
|
file = os.path.expanduser(file)
|
|
return file
|
|
|
|
def cancel_command(self, event=None):
|
|
self.quit()
|
|
|
|
def set_filter(self, dir, pat):
|
|
if not os.path.isabs(dir):
|
|
try:
|
|
pwd = os.getcwd()
|
|
except OSError:
|
|
pwd = None
|
|
if pwd:
|
|
dir = os.path.join(pwd, dir)
|
|
dir = os.path.normpath(dir)
|
|
self.filter.delete(0, END)
|
|
self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
|
|
|
|
def set_selection(self, file):
|
|
self.selection.delete(0, END)
|
|
self.selection.insert(END, os.path.join(self.directory, file))
|
|
|
|
|
|
class LoadFileDialog(FileDialog):
|
|
|
|
"""File selection dialog which checks that the file exists."""
|
|
|
|
title = "Load File Selection Dialog"
|
|
|
|
def ok_command(self):
|
|
file = self.get_selection()
|
|
if not os.path.isfile(file):
|
|
self.master.bell()
|
|
else:
|
|
self.quit(file)
|
|
|
|
|
|
class SaveFileDialog(FileDialog):
|
|
|
|
"""File selection dialog which checks that the file may be created."""
|
|
|
|
title = "Save File Selection Dialog"
|
|
|
|
def ok_command(self):
|
|
file = self.get_selection()
|
|
if os.path.exists(file):
|
|
if os.path.isdir(file):
|
|
self.master.bell()
|
|
return
|
|
if not messagebox.askyesno(
|
|
"Overwrite Existing File",
|
|
"Overwrite existing file %r?" % (file,),
|
|
icon=messagebox.WARNING, parent=self.top):
|
|
return
|
|
else:
|
|
head, tail = os.path.split(file)
|
|
if not os.path.isdir(head):
|
|
self.master.bell()
|
|
return
|
|
self.quit(file)
|
|
|
|
|
|
# For the following classes and modules:
|
|
#
|
|
# options (all have default values):
|
|
#
|
|
# - defaultextension: added to filename if not explicitly given
|
|
#
|
|
# - filetypes: sequence of (label, pattern) tuples. the same pattern
|
|
# may occur with several patterns. use "*" as pattern to indicate
|
|
# all files.
|
|
#
|
|
# - initialdir: initial directory. preserved by dialog instance.
|
|
#
|
|
# - initialfile: initial file (ignored by the open dialog). preserved
|
|
# by dialog instance.
|
|
#
|
|
# - parent: which window to place the dialog on top of
|
|
#
|
|
# - title: dialog title
|
|
#
|
|
# - multiple: if true user may select more than one file
|
|
#
|
|
# options for the directory chooser:
|
|
#
|
|
# - initialdir, parent, title: see above
|
|
#
|
|
# - mustexist: if true, user must pick an existing directory
|
|
#
|
|
|
|
|
|
class _Dialog(commondialog.Dialog):
|
|
|
|
def _fixoptions(self):
|
|
try:
|
|
# make sure "filetypes" is a tuple
|
|
self.options["filetypes"] = tuple(self.options["filetypes"])
|
|
except KeyError:
|
|
pass
|
|
|
|
def _fixresult(self, widget, 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:
|
|
result = result.string
|
|
except AttributeError:
|
|
# it already is a string
|
|
pass
|
|
path, file = os.path.split(result)
|
|
self.options["initialdir"] = path
|
|
self.options["initialfile"] = file
|
|
self.filename = result # compatibility
|
|
return result
|
|
|
|
|
|
#
|
|
# file dialogs
|
|
|
|
class Open(_Dialog):
|
|
"Ask for a filename to open"
|
|
|
|
command = "tk_getOpenFile"
|
|
|
|
def _fixresult(self, widget, result):
|
|
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
|
|
return _Dialog._fixresult(self, widget, result)
|
|
|
|
|
|
class SaveAs(_Dialog):
|
|
"Ask for a filename to save as"
|
|
|
|
command = "tk_getSaveFile"
|
|
|
|
|
|
# the directory dialog has its own _fix routines.
|
|
class Directory(commondialog.Dialog):
|
|
"Ask for a directory"
|
|
|
|
command = "tk_chooseDirectory"
|
|
|
|
def _fixresult(self, widget, result):
|
|
if not result:
|
|
result = '' # normalize the cancelled result (gh-103878)
|
|
else:
|
|
# convert Tcl path objects to strings
|
|
try:
|
|
result = result.string
|
|
except AttributeError:
|
|
# it already is a string
|
|
pass
|
|
# keep directory until next time
|
|
self.options["initialdir"] = result
|
|
self.directory = result # compatibility
|
|
return result
|
|
|
|
#
|
|
# convenience stuff
|
|
|
|
|
|
def askopenfilename(**options):
|
|
"Ask for a filename to open"
|
|
|
|
return Open(**options).show()
|
|
|
|
|
|
def asksaveasfilename(**options):
|
|
"Ask for a filename to save as"
|
|
|
|
return SaveAs(**options).show()
|
|
|
|
|
|
def askopenfilenames(**options):
|
|
"""Ask for multiple filenames to open
|
|
|
|
Returns a list of filenames or empty list if
|
|
cancel button selected
|
|
"""
|
|
options["multiple"]=1
|
|
return Open(**options).show()
|
|
|
|
# FIXME: are the following perhaps a bit too convenient?
|
|
|
|
|
|
def askopenfile(mode = "r", **options):
|
|
"Ask for a filename to open, and returned the opened file"
|
|
|
|
filename = Open(**options).show()
|
|
if filename:
|
|
return open(filename, mode)
|
|
return None
|
|
|
|
|
|
def askopenfiles(mode = "r", **options):
|
|
"""Ask for multiple filenames and return the open file
|
|
objects
|
|
|
|
returns a list of open file objects or an empty list if
|
|
cancel selected
|
|
"""
|
|
import warnings
|
|
warnings._deprecated(
|
|
"tkinter.filedialog.askopenfiles",
|
|
message=f"{warnings._DEPRECATED_MSG}; iterate over the names returned "
|
|
"by askopenfilenames() and open them instead",
|
|
remove=(3, 19))
|
|
|
|
files = askopenfilenames(**options)
|
|
return [open(filename, mode) for filename in files]
|
|
|
|
|
|
def asksaveasfile(mode = "w", **options):
|
|
"Ask for a filename to save as, and returned the opened file"
|
|
|
|
filename = SaveAs(**options).show()
|
|
if filename:
|
|
return open(filename, mode)
|
|
return None
|
|
|
|
|
|
def askdirectory (**options):
|
|
"Ask for a directory, and return the file name"
|
|
return Directory(**options).show()
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
# test stuff
|
|
|
|
def test():
|
|
"""Simple test program."""
|
|
root = Tk()
|
|
use_ttk = tkinter.BooleanVar(root, value=True)
|
|
|
|
def test_load():
|
|
print('load:', LoadFileDialog(root, use_ttk=use_ttk.get()).go(key='test'))
|
|
|
|
def test_save():
|
|
print('save:', SaveFileDialog(root, use_ttk=use_ttk.get()).go(key='test'))
|
|
|
|
def test_open():
|
|
print('open:', askopenfilename(filetypes=[('all files', '*')]))
|
|
|
|
def test_saveas():
|
|
print('saveas:', asksaveasfilename())
|
|
|
|
def test_directory():
|
|
print('directory:', askdirectory())
|
|
|
|
def test_directory_mustexist():
|
|
print('directory (mustexist):', askdirectory(mustexist=True))
|
|
|
|
tkinter.Checkbutton(root, text='Use themed (ttk) widgets',
|
|
variable=use_ttk).pack(fill=X)
|
|
tkinter.Button(root, text='LoadFileDialog', command=test_load).pack(fill=X)
|
|
tkinter.Button(root, text='SaveFileDialog', command=test_save).pack(fill=X)
|
|
tkinter.Button(root, text='askopenfilename', command=test_open).pack(fill=X)
|
|
tkinter.Button(root, text='asksaveasfilename', command=test_saveas).pack(fill=X)
|
|
tkinter.Button(root, text='askdirectory', command=test_directory).pack(fill=X)
|
|
tkinter.Button(root, text='askdirectory (mustexist)',
|
|
command=test_directory_mustexist).pack(fill=X)
|
|
tkinter.Button(root, text='Quit', command=root.quit).pack(fill=X)
|
|
root.mainloop()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test()
|