Table of contents
- Learn Tkinter by making a Text editor app
- What are we going to learn today -
- Three reasons why you should use Tkinter
- Three reasons when to Use Tkinter?
- Three reasons when Not to Use Tkinter?
- Before we begin - Best Practices
- Let's dive into action
- Defining all the sub-components and functions
- Defining the update_status() function for the Status Bar
- Defining the change_font() function for the View menu
- Defining the change_theme() function for the View menu
- Defining the show_about() function for the Help menu
- Defining the new, open, save, save_as functions for the File menu
- Defining the cut, copy, paste, undo, redo functions for the Edit menu
- Running the text editor app
Learn Tkinter by making a Text editor app
If you have come to this blog and are completely new to
tkinter
or a beginner inPython
, then this blog is exactly for you.
What are we going to learn today -
Today we will create a basic text editor using the
tkinter
module in Python.The editor offers a menu bar with the functionalities such as:
opening files
saving files
text editing commands like cut, copy, paste, undo, redo
font customization
theme switching - Dark and Light mode
This is how the end result would look like:
Three reasons why you should use Tkinter
1. Cross-platform
Applications developed with tkinter work on Windows, macOS, and Linux without any modifications.
2. Rich Widget Set
Tkinter provides an array of widgets (like buttons
, labels
, text areas
, and canvases
) and utilities for event handling
, geometry management
, etc.
3. Easy to Use
For beginners, tkinter offers a more gentle introduction to GUI programming than some other libraries because of its simplicity.
Three reasons when to Use Tkinter?
1. Simple Tools and Applications
If you're developing a small utility or a tool for personal use, or you're prototyping something quickly, tkinter is a good fit.
2. Educational Purpose
For teaching GUI programming or Python programming in general, tkinter serves as a beginner-friendly option.
3. Cross-platform Desktop Applications
If you aim to develop a lightweight application that should run on multiple platforms without much hassle, tkinter can be beneficial.
Three reasons when Not to Use Tkinter?
1. Modern Look and Feel
If you want an application with a very modern UI, you might find tkinter a bit lacking, as its widgets have a more traditional appearance.
2. Intensive Graphics
For applications that require advanced graphics, animations, or games, libraries like PyQt, Kivy, or Pygame might be better suited.
3. Complex Applications
For larger, more complex applications with a lot of features, more comprehensive frameworks like PyQt or wxPython might be more appropriate.
Before we begin - Best Practices
Believe in yourself, and don't give up mid-way, if there is a new concept, research about it or simply contact me - Mustafa Saifee
Complete the project app and upload it to your own GitHub repo and add it to your resume with the link to the repo
And most importantly, type everything manually without copy-pasting the code.
Let's dive into action
Installing tkinter
tkinter
is normally bundled with most standard Python installations, so there's usually no need to install it separately. However, in some scenarios, if it's missing, here is the command -
Windows Installation steps
pip install tk
Linux Installation steps
For Debian-based:
sudo apt-get install python3-tk
For Red Hat-based Linux:
sudo yum install python3-tkinter # For older versions with yum
Importing the libraries
We will start the development of the application, in your preferred text editor, create a new file called app.py
and open the file for further development.
import tkinter as tk
from tkinter import filedialog, messagebox
We have imported the necessary modules, and the other imports (filedialog and messagebox) help in dialog box functionalities like opening files or showing alerts.
Calling the main
function:
This code block initializes the root window, creates an instance of the TextEditor, and then enters the main event loop, waiting for user input.
if __name__ == "__main__":
root = tk.Tk()
editor = TextEditor(root)
root.mainloop()
This is how your code should look now:
import tkinter as tk
from tkinter import filedialog, messagebox
# All the rest of the code will be here
# We will write more classes and functions which will be between the two code pieces
if __name__ == "__main__":
root = tk.Tk()
editor = TextEditor(root)
root.mainloop()
Creating the class
and menu_bar
class TextEditor:
def __init__(self, root):
self.root = root
self.root.title("Tkinter Text Editor")
# Menu Bar
self.menu_bar = tk.Menu(root)
self.root.config(menu=self.menu_bar)
TextEditor
class will represent the main application or the text editor itself.self.root.title("Tkinter Text Editor")
- This line sets the title of the main window (root
) to"Tkinter Text Editor"
. When you run the application, the title bar of the window will display this text.self.menu_bar = tk.Menu(root)
- This creates a new Menu widget, which is the main menu bar of the application. This menu bar will be placed at the top of the root window and will later have various menu items (like File, Edit, etc.) added to it.self.root.config(menu=self.menu_bar)
- This line associates the newly created menu bar (self.menu_bar
) with the main window (self.root
). Theconfig
function is used to configurewidget
properties, and in this case, it's setting the main window's menu to beself.menu_bar
.
Everything we code henceforth should be inside class TextEditor
. So maintain the indentation and write the code one after the other as instructed.
Designing the File Menu components:
We want the file submenu to have New
, Open
, Save
, SaveAs
, and Exit
feature options.
NOTE: This is inside the
def __init__(self, root):
function.
# File Menu
self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="File", menu=self.file_menu)
self.file_menu.add_command(label="New", command=self.new_file)
self.file_menu.add_command(label="Open", command=self.open_file)
self.file_menu.add_command(label="Save", command=self.save_file)
self.file_menu.add_command(label="Save As", command=self.save_as_file)
self.file_menu.add_separator()
self.file_menu.add_command(label="Exit", command=root.quit)
tk.Menu(self.menu_bar, tearoff=0)
- This menu will become a dropdown list under the main menu bar (self.menu_bar
). Thetearoff=0
argument prevents the menu from being separated into its own window when dragged (a behavior seen in some older systems).self.menu_bar.add_cascade(label="File", menu=self.file_menu)
- Here, the"File"
submenu (self.file_menu
) is added to the main menu bar (self.menu_bar
). The add_cascade function is used to add a submenu to a menu. The label "File" is what you'll see in the main menu bar.self.file_menu.add_command(label="xxx", command=self.xxx_file)
- This adds a menu item labeled"xxx"
to the"File"
submenu. When this"xxx"
option is selected, the functionself.xxx_file
will be called. Theadd_command
function is used to add an actionable menu item. Replacingxxx
withNew
orOpen
will give the desired outputself.file_menu.add_separator()
- This adds a separator line in the "File" submenu. Just a visual element.self.file_menu.add_command(label="Exit", command=root.quit)
- Lastly, this adds an"Exit"
option to the"File"
submenu. When this option is selected, theroot.quit
function will be called, closing the application.
Designing the Edit Menu components:
The edit menu can have functions like Cut
, Copy
, Paste
, Undo
and Redo
.
NOTE: This is inside the
def __init__(self, root):
function.
# Edit Menu
self.edit_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu)
self.edit_menu.add_command(label="Cut", command=self.cut_text)
self.edit_menu.add_command(label="Copy", command=self.copy_text)
self.edit_menu.add_command(label="Paste", command=self.paste_text)
self.edit_menu.add_separator()
self.edit_menu.add_command(label="Undo", command=self.undo_text)
self.edit_menu.add_command(label="Redo", command=self.redo_text)
- As discussed previously, self.menu_bar.add_cascade() will add the Edit submenu to the Menu bar.
self.edit_menu.add_command()
will add respectiveCut
,Copy
,Paste
,Undo
andRedo
functions to theEdit
menuadd_separator()
funtion is a visual line separator.
Designing the View Menu and it's Submenu components:
TheView Menu
would be a little special than the other menus', we would want to add two more expandable submenus to the View Menu
. They are Font Submenu and Theme Submenu.
For the Font
Submenu, we will add font options like "Times", "Arial", "Helvetica".
For the Theme
Submenu, we will add two options to switch between Light
theme and Dark
theme.
NOTE: This is inside the
def __init__(self, root):
function.
# View Menu
self.view_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="View", menu=self.view_menu)
# Font submenu
self.font_var = tk.StringVar(value="Times")
self.fonts = ["Times", "Arial", "Helvetica"]
self.font_menu = tk.Menu(self.view_menu, tearoff=0)
for font in self.fonts:
self.font_menu.add_radiobutton(label=font, variable=self.font_var, command=self.change_font)
self.view_menu.add_cascade(label="Font", menu=self.font_menu)
# Theme submenu
self.theme_var = tk.StringVar(value="Light")
self.themes = {
"Light": {"bg": "white", "fg": "black"},
"Dark": {"bg": "black", "fg": "white"}
}
self.theme_menu = tk.Menu(self.view_menu, tearoff=0)
for theme, _ in self.themes.items():
self.theme_menu.add_radiobutton(label=theme, variable=self.theme_var, command=self.change_theme)
self.view_menu.add_cascade(label="Theme", menu=self.theme_menu)
Font
font_var = tk.StringVar(value="Times")
- We set the default font to "Times".self.fonts
- We provide the available fontsfont_menu.add_radiobutton(label=font, variable=self.font_var, command=self.change_font)
- For eachfont
in theself.fonts
list, a radio button is added toself.font_menu
. When a particular font is chosen,self.font_var
will be updated to that font, and the self.change_font
function will be executed to change the editor's font.
Theme
theme_var = tk.StringVar(value="Light")
- Setting the default theme toLight
self.themes= {}
- A dictionaryself.themes
is defined with two themes,"Light"
and"Dark"
. Each theme has background (bg
) and foreground (fg
) color values.self.theme_menu.add_radiobutton(label=theme, variable=self.theme_var, command=self.change_theme)
- For each theme in self.themes, a radio button is added to self.theme_menu. When a theme is selected, self.theme_var will be updated to that theme, and the self.change_theme function will be executed to change the editor's theme.
Designing the Help Menu
Our Help
menu would simply have an About
command which will trigger a window pop-up with some introductory text.
NOTE: This is inside the
def __init__(self, root):
function.
# Help Menu
self.help_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="Help", menu=self.help_menu)
self.help_menu.add_command(label="About", command=self.show_about)
In the above code we simply add the Help
Menu to the Main Menu
bar, and add a cascaded option of About
to the Help
menu. The self.show_about
calls the show_about
function.
Designing the Text Area, Scrollbar and the Status bar
Text Area and Scrollbar - This section is about creating a text widget (or text area) where users can input and edit text, and an associated vertical scrollbar to scroll through the text if it exceeds the visible area of the widget.
Status bar - A status bar is generally a small section at the bottom of a GUI application that provides quick info or status updates. In this case, it shows the current line and column position of the cursor in the text widget.
NOTE: This is inside the
def __init__(self, root):
function.
# Text Area and Scrollbar
self.text_scroll = tk.Scrollbar(root)
self.text_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.text_area = tk.Text(root, yscrollcommand=self.text_scroll.set, wrap=tk.WORD, undo=True, font=(self.font_var.get(), 12))
self.text_area.pack(fill=tk.BOTH, expand=1)
self.text_scroll.config(command=self.text_area.yview)
self.text_area.bind('<KeyRelease>', self.update_status)
# Status Bar
self.status = tk.Label(root, text='Line: 1 | Column: 1', anchor=tk.W)
self.status.pack(side=tk.BOTTOM, fill=tk.X)
Scrollbar
- We start of by
Scrollbar
initialization -tk.Scrollbar(root)
self.text_scroll.pack()
- Thepack()
function packs (or places) the scrollbar to the right side (tk.RIGHT
) of the parent widget (root
). Thefill=tk.Y
means that the scrollbar will fill the entire vertical space of its parent container.
Text Area
tk.Text(root, yscrollcommand=self.text_scroll.set, wrap=tk.WORD, undo=True, font=(self.font_var.get(), 12))
-yscrollcommand=self.text_scroll.set
: This links the scrollbar's movement to the text widget's vertical scrolling.wrap=tk.WORD
: Text will wrap at word boundaries (and not break words in half).undo=True
: Enables the undo feature.font=(self.font_var.get(), 12)
: Sets the initial font of the text to the value inself.font_var
(which defaults to"Times"
) with a font size of12
.
- Extra space allotment for the text area using the
expand=1
-self.text_area.pack(fill=tk.BOTH, expand=1)
Linking Text Area with Scrollbar
- Then we link the scrollbar to the text area -
self.text_scroll.config(command=self.text_area.yview)
self.text_area.bind('<KeyRelease>', self.update_status)
- The text widget is bound to theself.update_status
function such that every time a key is released within thetext widget
, the function is called. This is likely used to update the status bar with the current line and column position (as seen in the next section).
Status Bar
tk.Label(root, text='Line: 1 | Column: 1', anchor=tk.W)
- A label widget is initialized with a default text showing the cursor is at Line 1, Column 1. This label is assigned to the variable self.status. The anchor=tk.W ensures the text aligns to the left (West).pack(side=tk.BOTTOM, fill=tk.X)
- This function places the label at the bottom (tk.BOTTOM) of the parent widget (root). The fill=tk.X ensures the label fills the entire horizontal space of its parent container.
Defining all the sub-components and functions
We have completeted the application's interface and visual options. Now we need to define all the functions that these components are calling.
Henceforth Rest of the code is outside the def __init__(self, root):
function but inside the class TextEditor
.
Defining the update_status() function for the Status Bar
NOTE: This is inside the
class TextEditor
as a separate function.
def update_status(self, event=None):
row, col = self.text_area.index(tk.INSERT).split('.')
self.status.config(text=f'Line: {row} | Column: {col}')
self.text_area.index(tk.INSERT)
: This gets the current position of the insertion cursor in the text_area widget. The insertion cursor is the blinking cursor indicating where the next characters will be inserted when you type.- The position is returned in the format row.column. For example, if the insertion cursor is at the third row and fifth column, the index method would return the string "3.5".
.split('.')
: This splits the returned string into two parts at the dot. Using the previous example, "3.5" would be split into ["3", "5"].row, col = ...
: The split result is then unpacked into two separate variables: row and col.
Defining the change_font() function for the View menu
NOTE: This is inside the
class TextEditor
as a separate function.
def change_font(self):
self.text_area.config(font=(self.font_var.get(), 12))
self.text_area.config()
: This method is used to modify the configurations (or attributes) of a Tkinter widget, in this case, thetext_area
widget which is of typetk.Text
.font=(self.font_var.get(), 12)
: Thefont
attribute takes a tuple, where the first element is the font name and the second element is the font size.self.font_var.get()
: This retrieves the current value stored in theself.font_var
variable, which is of typetk.StringVar()
. In the context of the program you provided, the user can choose a font from a menu, and when they do, the value ofself.font_var
gets set to the name of the chosen font (e.g., "Times", "Arial", or "Helvetica").12
: This is the font size. The method sets the font size to 12 points regardless of which font is chosen.
Defining the change_theme() function for the View menu
NOTE: This is inside the
class TextEditor
as a separate function.
def change_theme(self):
theme = self.theme_var.get()
bg_color, fg_color = self.themes[theme]["bg"], self.themes[theme]["fg"]
self.text_area.config(bg=bg_color, fg=fg_color)
- The purpose of this method is to adjust the theme of the
text_area
widget, specifically changing its background (bg
) and foreground (fg
) colors. - The current value of the
self.theme_var
is fetched and assigned to the variabletheme
. Theself.theme_var
is an instance oftk.StringVar()
, which is a special type of string variable inTkinter
used for holding string values. In the provided context,self.theme_var
holds the name of the currently selected theme (e.g., "Light" or "Dark"). self.themes
is a dictionary that holds the details for each theme, specifically their background (bg
) and foreground (fg
) colors.self.themes[theme]
fetches the dictionary of the specific theme (either "Light" or "Dark" in this case).["bg"]
and["fg"]
retrieve the background and foreground colors respectively for the selected theme.bg=bg_color
: Sets the background color of thetext_area
to the value ofbg_color
.fg=fg_color
: Sets the foreground (text) color of thetext_area
to the value offg_color
.
Defining the show_about() function for the Help menu
NOTE: This is inside the
class TextEditor
as a separate function.
def show_about(self):
messagebox.showinfo("About", "This is a simple Text Editor application using tkinter!")
messagebox.showinfo
: This is a function from the messagebox module in tkinter that displays an informational message to the user in a pop-up dialog box."About"
: This is thetitle
of thepop-up
dialog box."This is a simple Text Editor application using tkinter!"
: This is the message/content that will be displayed within thepop-up
dialog box.
Defining the new, open, save, save_as functions for the File menu
new_file
is for starting a fresh, new document.open_file
is for opening an existing document.save_file
is for saving the current document, either updating the existing file or prompting for a filename if it's a new document.save_as_file
is for saving the current document under a new name or location.
NOTE: This is inside the
class TextEditor
as separate functions.
def new_file(self):
self.text_area.delete(1.0, tk.END)
def open_file(self):
file_path = filedialog.askopenfilename()
if file_path:
with open(file_path, 'r') as file:
self.text_area.delete(1.0, tk.END)
self.text_area.insert(tk.INSERT, file.read())
def save_file(self):
file_path = filedialog.asksaveasfilename(defaultextension=".txt")
if file_path:
with open(file_path, 'w') as file:
file.write(self.text_area.get(1.0, tk.END))
def save_as_file(self):
file_path = filedialog.asksaveasfilename(defaultextension=".txt")
if file_path:
with open(file_path, 'w') as file:
file.write(self.text_area.get(1.0, tk.END))
new_file()
self.text_area.delete(1.0, tk.END)
: This line clears the entire text area. Thedelete
method of theText
widget removes content. Here, it removes content starting from the very first character (1.0
signifies the first line and the zeroth character of that line) up to the end (tk.END
).
open_file()
filedialog.askopenfilename()
: This opens a file dialog where users can select a file to open. It then returns the path to the selected file.- The
if
statement checks if a file was actually selected (i.e., the user did not cancel the file dialog). - If a file was selected, it's opened in reading mode (
'r'
). - The current content of the
text_area
is then cleared usingdelete
. - The content of the opened file is then read using
file.read()
and inserted into thetext_area
.
save_file()
save_file
function: This function is for saving the content of the text editor to a file.- It function opens a dialog allowing the user to specify where and with what name they wish to save the file. If no extension is provided by the user,
.txt
is used by default. - If a location and filename are chosen, the content of the
text_area
is written to this file.
save_as_file()
save_as_file
method: This method is similar to thesave_file
method but is typically used to save the file with a new name or at a different location.- Like the
save_file
method, it opens a dialog for the user to select a location and filename. - And like the
save_file
method, if a location and filename are chosen, the content of thetext_area
is saved to this new location.
Defining the cut, copy, paste, undo, redo functions for the Edit menu
cut_text
cuts the selected text.copy_text
copies the selected text.paste_text
pastes the clipboard content.undo_text
undoes the last change.redo_text
redoes a previously undone change.
NOTE: This is inside the
class TextEditor
as separate functions.
def cut_text(self):
self.text_area.event_generate("<<Cut>>")
def copy_text(self):
self.text_area.event_generate("<<Copy>>")
def paste_text(self):
self.text_area.event_generate("<<Paste>>")
def undo_text(self):
self.text_area.edit_undo()
def redo_text(self):
self.text_area.edit_redo()
cut_text()
cut_text
method: This method is designed to cut (remove and copy to clipboard) the currently selected text in the text editor.self.text_area.event_generate("<<Cut>>")
: Theevent_generate
method is used to simulate a specific event on a widget. In this case, the"<<Cut>>"
event is generated on thetext_area
, which is equivalent to performing a cut operation on the selected text.
copy_text()
copy_text
method: This method copies the currently selected text in the text editor to the clipboard without removing it.self.text_area.event_generate("<<Copy>>")
: This line simulates the"<<Copy>>"
event on thetext_area
, copying the selected text to the clipboard.
paste_text()
paste_text
method: This method is for pasting the content currently on the clipboard into the text editor at the current cursor location.self.text_area.event_generate("<<Paste>>")
: This simulates the"<<Paste>>"
event on thetext_area
, pasting the clipboard content at the cursor's position.
undo_text()
undo_text
method: This method undoes the most recent change in the text editor.self.text_area.edit_undo()
: Theedit_undo
method of theText
widget is called to undo the last change. It's important to note that theundo
attribute of theText
widget must be set toTrue
for this method to work, which it was in the previously provided code.
redo_text()
redo_text
method: This method redoes a change that was previously undone in the text editor.self.text_area.edit_redo()
: Theedit_redo
method of theText
widget is called to redo the previously undone change. Like the undo operation, theundo
attribute of theText
widget must be enabled.
If you wish to have the complete code, it is available at - Click Here
That was fun. Finally, we have completed the text editor. So let's execute it.
Running the text editor app
Terminal
On the terminal type the following:
python app.py
If the above command doesn't work replace python
with py
OR python3
Visual Studio Code
From the Menu bar, click on Run
> Run Without Debugging
OR
Press Ctrl
+ F5
I hope this tutorial was helpful and you learned the basic concepts of Tkinter
library.
If you have any feedback, suggestion or request, reach out to me -
Linkedin: linkedin.com/in/saifeemustafa
Twitter: twitter.com/mustafasaifee_