Working with forms and dialogs

The goal for this iteration is to implement the functionality of the File menu options: Open, Save, and Save As.

We can implement these dialogs by using the standard Tkinter widgets. However, since these are so commonly used, a specific Tkinter module called filedialog has been included in the standard Tkinter distribution.

Here's an example of a typical filedialog:

Tkinter defines the following common use cases for filedialogs:

Functions Description
askopenfile This returns the opened file object
askopenfilename This returns the filename string, not the opened file object
askopenfilenames This returns a list of filenames
askopenfiles This returns a list of open file objects or an empty list if
Cancel is selected
asksaveasfile This asks for a filename to save as and returns the opened
file object
asksaveasfilename This asks for a filename to save as and returns the filename
askdirectory This asks for a directory and returns the directory name

 

The usage is simple. Import the filedialog module and call the required function. Here's an example:

import tkinter.filedialog

We then call the required function using the following code:

file_object = tkinter.filedialog.askopenfile(mode='r')

Or, we use this code:

my_file_name = tkinter.filedialog.askopenfilename()

The mode='r' option specified in the preceding code is one of many configurable options that are available for dialogs. 

You can specify the following additional options for filedialog:

File dialog Configurable options
askopenfile
(mode='r', **options)
parent, title, message, defaultextensionfiletypes, initialdir, initialfile, and multiple
askopenfilename
(**options)
parent, title, message, defaultextensionfiletypes, initialdir, initialfile, and multiple
asksaveasfile
(mode='w', **options)
parent, title, message, defaultextensionfiletypes, initialdir, initialfile, and multiple
asksaveasfilename
(**options)
parent, title, message, defaultextensionfiletypes, initialdir, initialfile, and multiple
askdirectory
(**options)
parent, title, and initialdir must exist

 

Equipped with a basic understanding of the filedialog module, let's now have a look at its practical usage. We'll begin by implementing the File Open feature. 

Let's start by importing the required modules, as follows:

import tkinter.filedialog
import os # for handling file operations

Next, let's create a global variable, which will store the name of the currently open file, as follows:

file_name = None
The use of global variables is generally considered bad programming practice because it is very difficult to understand a program that uses lots of global variables.
A global variable can be modified or accessed from many different places in the program. Therefore, it becomes difficult to remember or work out every possible use of the variable.
A global variable is not subject to access control, which may pose security hazards in certain situations, say when this program needs to interact with third-party code.
However, when you work on programs in a procedural style such as this one, global variables are sometimes unavoidable.
An alternative approach to programming involves writing code in a class structure (also called object-oriented programming), where a variable can only be accessed by members of predefined classes. We will see a lot of examples of object-oriented programming in the chapters that follow.

The following code is present in open_file (2.07.py):

def open_file(event=None):
input_file_name =
tkinter.filedialog.askopenfilename(defaultextension=".txt",
filetypes=[("All Files", "*.*"),("Text Documents", "*.txt")])
if input_file_name:
global file_name
file_name = input_file_name
root.title('{} - {}'.format(os.path.basename(file_name),PROGRAM_NAME))
content_text.delete(1.0, END)
with open(file_name) as _file:
content_text.insert(1.0, _file.read())
on_content_changed()

Modify the Open menu to add a callback command to this newly defined method, as follows:

file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline =0, command=open_file)

The following is a description of the preceding code:

  • We declared a file_name variable in the global scope to keep track of the filename of the opened file. This is required to keep track of whether a file has been opened. We need this variable in the global scope as we want this variable to be available to other methods, such as save() and save_as().
  • Not specifying it as global would mean that it is only available within the function. So, the save() and save_as() functions would not be able to check whether a file is already open in the editor.
  • We use askopenfilename to fetch the filename of the opened file. If a user cancels opening the file or no file is chosen, the file_name returned is None. In that case, we do nothing. 
  • However, if filedialog returns a valid filename, we isolate the filename using the os module and add it as the title of the root window.
  • If the Text widget already contains some text, we delete it all.
  • We then open the given file in read mode and insert its content into the Content widget.
  • We use the context manager (the with command), which takes care of closing the file properly for us, even in the case of an exception.
  • Finally, we add a command callback to the File | Open menu item.

This completes the coding of File | Open. If you now navigate to File | Open, select a text file, and click on Open, the content area will be populated with the content of the text file.

Next, we will have a look at how to save a file. There are two aspects to saving a file:

  • Save
  • Save As

If the Content text widget already contains a file, we do not prompt the user for a filename. We simply overwrite the contents of the existing file. If there is no filename associated with the current content of the text area, we prompt the user with a Save As dialog. Moreover, if the text area has an open file and the user clicks on Save As, we still prompt them with a Save As dialog to allow them to write the contents to a different filename.

The code for save and save_as is as follows (2.07.py):

def save(event=None):
global file_name
if not file_name:
save_as()
else:
write_to_file(file_name)
return "break"

def save_as(event=None):
input_file_name = tkinter.filedialog.asksaveasfilename
(defaultextension=".txt", filetypes=[("All Files", "*.*"),
("Text Documents", "*.txt")])
if input_file_name:
global file_name
file_name = input_file_name
write_to_file(file_name)
root.title('{} - {}'.format(os.path.basename(file_name),PROGRAM_NAME))
return "break"

def write_to_file(file_name):
try:
content = content_text.get(1.0, 'end')
with open(file_name, 'w') as the_file:
the_file.write(content)
except IOError:
pass
# pass for now but we show some warning - we do this in next section

Having defined the save and save_as functions, let's connect them to the respective menu callback:

file_menu.add_command(label='Save', accelerator='Ctrl+S',  compound='left', image=save_file_icon,underline=0, command= save)
file_menu.add_command(label='Save as', accelerator='Shift+Ctrl+S', command= save_as)

The following is a description of the preceding code:

  • The save function first tries to check whether a file is open. If a file is open, it simply overwrites the contents of the file with the current contents of the text area. If no file is open, it simply passes the work to the save_as function.
  • The save_as function opens a dialog by using asksaveasfilename and tries to get the filename provided by the user for the given file. If it succeeds, it opens the new file in write mode and writes the contents of the text under this new filename. After writing, it closes the current file object and changes the title of the window to reflect the new filename.
  • If the user does not specify a filename or the user cancels the save_as operation, it simply ignores the process by using a pass command.
  • We added a write_to_file(file_name) helper function to do the actual writing to the file.

While we are at it, let's complete the functionality of File | New. The code is simple (2.07.py):

def new_file(event=None):
root.title("Untitled")
global file_name
file_name = None
content_text.delete(1.0,END)

Now, add a callback command to this new function to the File | New menu item:

file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0, command=new_file)

The following is a description of the preceding code:

  1. The new_file function begins by changing the title attribute of the root window to Untitled.
  2. It then sets the value of the global filename variable to None. This is important because the save and save_as functionalities use this global variable name to track whether the file already exists or is new.
  3. The function then deletes all the contents of the Text widget, creating a fresh document in its place.

Let's wrap up this iteration by adding keyboard shortcuts for the newly created features (2.07.py):

content_text.bind('<Control-N>', new_file)
content_text.bind('<Control-n>', new_file)
content_text.bind('<Control-O>', open_file)
content_text.bind('<Control-o>', open_file)
content_text.bind('<Control-S>', save)
content_text.bind('<Control-s>',save)

In this iteration, we implemented the coding functionality for the New, Open, Save, and Save As menu items.  More importantly, we saw how to use the filedialog module to achieve certain commonly used file features in the program. We also had a look at how to use indexing to achieve a wide variety of tasks for programs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.34.197