Reading User Files Using Tkinter In Python

Reading User Files With Tkinter In Python

7th August 2022 - 13 minutes read time

The Tkinter library in Python has a number of file dialogs that allow programs to ask for a file from a user. Using these dialogs it is possible to accept a file from a user and read the contents of that file.

Tkinter comes with a number of different dialogs that have a number of options. These allow users to load directories and files into your python applications, or to point to files that they want to save information into.

In this article we will be concentrating on reading information from files and so the save dialogs will not feature here.

Let's start off by creating a simple Tkinter application that has a button and a text area that we will read the contents of a file into. We will add the file dialog to the button event later.

import tkinter as tk
from tkinter import filedialog as fd

class FileDialogDemo(tk.Tk):

    def __init__(self):
        super().__init__()

        # Create a window of 600x300 and center this on the screen.
        width = 600
        height = 300
        
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x = (screen_width/2) - (width/2)
        y = (screen_height/2) - (height/2)
        
        self.geometry('%dx%d+%d+%d' % (width, height, x, y))

        # Create a file dialog button.
        self.button = tk.Button(self, text='Click to Open File')
        self.button.config(command=lambda filepath='.': self.filehandler(filepath))
        self.button.pack(fill=tk.X)

        # Create a blank text area.
        self.file_contents = tk.Text()
        self.file_contents.pack(fill=tk.BOTH, expand=True)
        self.title("File Dialog")
        
    # Callback for the file dialog button.
    def filehandler(self, filepath):
      pass

if __name__ == "__main__":
    file_dialog_demo = FileDialogDemo()
    file_dialog_demo.mainloop()

This creates an application that looks like this.

A Python Tkinter program, ready to accept a file input from a user.

When the user clicks on the "Click to Open File" button it does nothing, but first, let's look at the available functions and options for us to use here.

The File Dialog Functions

Tkinter comes with a number of functions that we can call to open a dialog for a user to add a file to a program.

  • tkinter.filedialog.askopenfilename() - This will return the name of the file added as a single value in a tuple data structure. This file name is then used to open the file and perform actions on it.
  • tkinter.filedialog.askopenfilenames() - This is the same as askopenfilename(), but allows for multiple files to be added at once.
  • tkinter.filedialog.askopenfile() - This will return an open file handler for the file which you can then read from.
  • tkinter.filedialog.askopenfiles() - This is same as the askopenfile(), but allows for multiple files to be added at once.
  • tkinter.filedialog.askdirectory() - This is the same as askopenfilename() and askopenfilenames() but in this case will only look at directories. The return here is the name of the directory.

There also exists tkinter.filedialog.asksaveasfilename() and tkinter.filedialog.asksaveasfile() that can be used to save information to a file, but these won't be addressed in this article since there is a little bit of complexity surrounding them.

To call these functions we just need to import the filedialog module from Tkinter and call them, this will automatically open the file dialog.

from tkinter import filedialog as fd

...

file = fd.askopenfilename()

In this case, the "file" variable now contains a tuple containing the name of the file.

The File Dialog Options

These methods can also take some parameters.

  • parent - The window to place the file open dialog on top of.
  • title - The title of the file dialog.
  • initialdir - The initial directory that the file dialog starts in.
  • initialfile - The initial file selected upon opening of the file dialog
  • filetypes - A sequence of tuples that show what file types can be allowed. The * can be used as a wild card. This can't be used on the askdirectory() function.
  • defaultextension - The default extension to append to file (save dialogs only).
  • multiple - Allows the selection of multiple files, which allows the askopenfilename() to act like the askopenfilenames() dialog. This can't be used on the askdirectory() function.

The options are passed as parameters when calling the file dialog functions. In the following example we are opening the file dialog on top of the current window using "parent", with the title of "Open file" and opened in the current directory that the program is running in. 

files = fd.askopenfile(
  parent=self,
  title="Open file",
  initialdir='.'
)

For example, to restrict the file types to text or python files use the following arguments.

filetypes = (
  ('text files', '*.txt'),
  ('python files', '*.py')
)

files = fd.askopenfile(
  filetypes=filetypes,
)

You can allow different types of a file by passing in a space separated list of file extensions or all files by just passing in *.*.

filetypes = (
   ('image files', '*.png *.jpg *.jpeg *.gif'),
   ('All files', '*.*')
)

files = fd.askopenfile(
  filetypes=filetypes,
)

With that information in hand we can now look at creating our file dialogs. The different functions return different things so I'll address askopenfilename() first.

Using askopenfilename()

We haven't implemented our button event yet, so let's do that and implement askopenfilename() to load a file into the text widget.

When we use the askopenfilename() functions we receive a string points to the absolute path of the file on your system. Using this information we then open up the file and read the contents into the text widget.

    # Callback for the file dialog button.
    def filehandler(self, initial_filepath):

        filetypes = [
            ('text files', '*.txt')
        ]

        file_references = fd.askopenfilename(
            parent=self,
            title="Open file",
            initialdir=initial_filepath,
            filetypes=filetypes
        )

        if (len(file_references) > 0):
            # Clear the contents of the file_contents widget.
            self.file_contents.delete('1.0', tk.END)

            content = ''

            # Read the contents of the file(s).
            for file_reference in file_references:
                with open(file_reference, encoding="utf-8") as f:
                    content = content + f.read()

            # Write the content into the text widget.
            self.file_contents.insert(tk.END, content)

Running the program will show the same interface as before, but in this case when we click on the "Click to Open File" button a file dialog will open up. How this looks will depend on the operating system you are using, but here is what it looks like on my system, which runs OsX.

A Python Tkinter program, showing an opened file dialog.

I opened up a file containing the GNU public licence as a quick demo. The contents of the file is then loaded and added to the text widget, which looks like this.

Python Tkinter file load program, showing a file loaded into the text widget.

Note that if you use the "multiple" option in askopenfilename() then it will act in much the same way as askopenfiles(). The data returned will even be the same.

Using askopenfilenames()

When we use the askopenfilenames() functions we receive a tuple containing strings that point to the absolute path of the file on your system. Using this information we then open up the file and read the contents of each of the passed files into the text widget.

    # Callback for the file dialog button.
    def filehandler(self, initial_filepath):

        filetypes = [
            ('text files', '*.txt')
        ]

        file_references = fd.askopenfilenames(
            parent=self,
            title="Open file",
            initialdir=initial_filepath,
            filetypes=filetypes
        )

        if (len(file_references) > 0):
            # Clear the contents of the file_contents widget.
            self.file_contents.delete('1.0', tk.END)

            content = ''

            # Read the contents of the file(s).
            with open(file_reference, encoding="utf-8") as f:
                content = content + f.read()

            # Write the content into the text widget.
            self.file_contents.insert(tk.END, content)

Using askopenfile()

The askopenfile() function returns an open file reference and so is treated slightly differently to the askopenfilename() function. As the file has already been opened we can just read the contents of the file directly into the text widget.

    def filehandler(self, initial_filepath):
        filetypes = (
            ('text files', '*.txt'),
        )

        file_reference = fd.askopenfile(
            parent=self,
            title="Open file",
            initialdir=initial_filepath,
            filetypes=filetypes,
        )

        if (file_reference is not None):
            # Clear the contents of the file_contents widget.
            self.file_contents.delete('1.0', tk.END)
            
            # Write the content into the text widget.
            self.file_contents.insert(tk.END, file_reference.read())

Note that just as in askopenfilenames(), if you pass in the mulitple option to this function you can make it act like askopenfiles(). It will even return the same datatype.

Using askopenfiles()

The askopenfiles() function returns a tuple containing references to the open files. Here, we need to loop through the file references and read each one into a content variable. We can then load that content variable into the content of the text widget.

   def filehandler(self, initial_filepath):
        filetypes = (
            ('text files', '*.txt'),
        )

        file_references = fd.askopenfiles(
            parent=self,
            title="Open file",
            initialdir=initial_filepath,
            filetypes=filetypes,
        )

        if (len(file_references) > 0):
            # Clear the contents of the file_contents widget.
            self.file_contents.delete('1.0', tk.END)
            
            content = ''

            for file_reference in file_references:
                content = content + file_reference.read()

            # Write the content into the text widget.
            self.file_contents.insert(tk.END, content)

Using askdirectory()

Finally, there is the askdirectory() function. This allows users to select a directory on their system and will return a string that points to the absolute location of that directory on their system. Using this information we then loop through the files in that directory and read their contents into the content variable.

There are a few different ways to read the contents of a directory in Python. I have chosen the glob method from the pathlib module in this example.

from pathlib import Path

....

   def filehandler(self, initial_filepath):
        directory_name = fd.askdirectory(
            parent=self,
            title="Open file",
            initialdir=initial_filepath
        )

        # Clear the contents of the file_contents widget.
        self.file_contents.delete('1.0', tk.END)

        content = ''
        
        # Read every file in the directory into the content variable.
        for f in Path(directory_name).glob('*.txt'):
            content = content + f.read_text()

        # Write the content into the text widget.
        self.file_contents.insert(tk.END, content)

Conclusion

As you can see there are a number of ways to accept file information from your users in Tkinter. What you use will depend on what you need to do, but the main difference is the data returned by these functions. You'll either receive a string, a file reference or a tuple containing either a string or a file reference.

As with all user interaction, it's a really good idea to make sure that the file you accept exists and has the correct data before using it. If the file doesn't exist or is corrupted or isn't the correct type then you need to fail gracefully and show an error message stating what the problem was.

More in this series

Add new comment

The content of this field is kept private and will not be shown publicly.