Let's Create a Toy Markdown Editor with Python Tkinter!
Tags: python, beginners, tkinter, markdown
Published: 2020-01-09 12:21:19

---
title: 
published: true
description: 
tags: python , beginner , tkinter  , markdown
cover_image: https://thepracticaldev.s3.amazonaws.com/i/86wxavm5hk85nia53ykr.png
canonical_url: https://dev.palashbauri.in/lets-create-a-toy-markdown-editor-with-python-tkinter-ck5rrcpud075fqps1e9opvjmo
---
Markdown Editor are one of the trending things nowadays. Everybody is creating a markdown editor, some of them are innovative but some of them are boring. In the meantime, I always been a fan of doing things which won't be done by others. (I will explain why other don't want to build markdown editor with Tkinter)

If you already are familiar with Python and Tkinter you can easily get into this guide.

> _But If you are just starting out with Python and/or Tkinter, you can check out these :_ **Python Tutorials:** [FreeCodeCamp Python Tutorial](https://www.freecodecamp.org/news/best-python-tutorial/) , [Python 3 Playlist by sentdex](https://www.youtube.com/playlist?list=PLQVvvaa0QuDeAams7fkdcwOGBpGdHpXln) , [FreeCodeCamp Python for Beginners](https://www.youtube.com/watch?v=rfscVS0vtbw) etc. (More can be found one google away)
> **Tkinter Tutorials:** [Tkinter Basics](https://pythonprogramming.net/python-3-tkinter-basics-tutorial/) , [FreeCodeCamp Tkinter Course](https://www.youtube.com/watch?v=YXPyB4XeYLA) , [TheNewBoston Tkinter Playlist](https://www.youtube.com/playlist?list=PL6gx4Cwl9DGBwibXFtPtflztSNPGuIB_d) etc. (More can be found one google away)

So, before we start, I want to explain why people don't want to build markdown editors with tkinter. Because there's no default easy way to display html output of the markdown input. There's not even a default tkinter widget to display html data. You can just simply write/edit markdown but there's no easy way to display the output inside your application.

But one day, while I was roaming the streets of Internet I found Something Interesting , [**tk_html_widgets**](https://github.com/paolo-gurisatti/tk_html_widgets) which can display html output! But ofcourse it did have some problems: the fonts were too small, and had no support for attaching remote photos. So as usual I created my own fork and fixed some issues and kinda improved the stability and named it : [tkhtmlview](https://github.com/bauripalash/tkhtmlview/). 😎

Ugh, I think I'm boring you 😅, so let's stop talking and start building.

#### 🛠️ Start Building:

First make sure you have Python 3 and Tkinter installed, if not you can download them from here [python.org/downloads](https://www.python.org/downloads) (Tkinter is already packed with Python).

Other Things we will need are **tkhtmlview** and **markdown2**. You can install them by running `pip install tkhtmlview markdown2` or `pip3 install tkhtmlview markdown2` (If you have multiple versions of Python)

Now fire up your favorite editor or IDE and create a new file (eg. `tdown.py` (I named the editor _tdown_)).

We will start by importing necessary libraries.

```python
from tkinter import *
from tkinter import font , filedialog
from markdown2 import Markdown
from tkhtmlview import HTMLLabel
```
In the first line we import everything (almost) from tkinter package.
In the second line we import font and filedialog. `font` is needed to style (eg. Font , Font Size) our input field, and filedialog is imported to open files markdown files for editing and/or save our markdown file.
in 3rd line, Markdown is imported to help us convert our markdown source to html and display on output field using HTMLLabel which we import on 4th line.

After that, we will create a frame class called Window which will be inherited from tkinters's **Frame** class and hold our input and output fields.

```python

class Window(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.master = master
        self.myfont = font.Font(family="Helvetica", size=14)
        self.init_window()

    def init_window(self):
        self.master.title("TDOWN")
        self.pack(fill=BOTH, expand=1)

```

Here in this codeblock, we first define a class called Window which inherits tkinter's Frame widget class. Now in the initialization function we take master as a argument which will serve as the parent of the frame, in the next line we initialize a Frame. Next we declare a custom font object called `self.myfont` with font family of **Helvetica** (You can choose any font family you want) and size of _14_ which will be used in our markdown input field, finally we call the _init_window_ function where we will put the heart of our application.

In the _init_window_ function we first set the title of the window as **TDOWN** and in the next line `self.pack(fill=BOTH, expand=1)` , we tell your Frame to take the full space of our window. We set `fill` keyword argument to `BOTH` which is actually being import from tkinter library. It tells the frame to fill the window both horizontally and vertically , and the `expand` keyword argument is set to 1 (signifies **True**) and tells our Frame to be expandable , in simple terms the Frame will be filling the window no matter if we stretch the window size , or maximize it.

Now if you run the `tdown.py` script you'll not see anything , because we have only defined the class but never called it.
To fix this we are gonna put this at the end of our script

```python
root = Tk()
root.geometry("700x600")
app = Window(root)
app.mainloop()
```

Here we create a Tk object and store it in root variable which will serve as the root of our Window class. Next we set the geometry of our window to 700x600, 700 is height and 600 is width of the window. In the next line you can see , we are creating a Window object, pushing **root** variable as _root_ of the frame and storing it in a variable called **app**. Next thing we do is just call the mainloop function which tells our app to run! 😊

Now run the `tdown.py` script! You will see a blank window like this if you did everything correctly:

![Blank Tkinter Frame](https://thepracticaldev.s3.amazonaws.com/i/w2cmd4lkqz2ag1u4cimk.png)

But it's just a blank window, to write something on the window we need add Text Field where will write our markdown. To do that we are gonna use **Text** widget from tkinter.

```python
...
def init_window(self):
    self.master.title("TDOWN")
    self.pack(fill=BOTH, expand=1)

    self.inputeditor = Text(self, width="1")
    self.inputeditor.pack(fill=BOTH, expand=1, side=LEFT)
```

> Don't get confused with the **...** (three dots) , I put them there only to signify that there are multiple lines of code before this code block.

Here we create a Text widget with width of **1**. Don't scratch your head, here sizings are done using ratios. You'll understand it more clearly in next few seconds when we will put the output box. 😁
We then pack it into the Frame and tell it to be both Horizontally and Vertically stretchable.

When you run the script, you'll see a Multiline Input Field has taken over our whole ~~World~~ Window. If you start writing in it, you may notice that the characters are so tiny!

![Input Field Has Taken Over the Whole Window!](https://thepracticaldev.s3.amazonaws.com/i/5lk52zviyi1up2x9by9o.png)

I already knew this problem will arise, that's why I told you earlier to create a custom font object (_self.myfont_), now if you do something like this:

```python

self.inputeditor = Text(self, width="1" , font=self.myfont)
```

> (Here, we tell our Text widget to use our custom font instead of the default tiny one!)

Font size of the input field will be increased to 14. Run the script to check if everything worked perfectly!

![Font Size has been increased to 14](https://thepracticaldev.s3.amazonaws.com/i/aomeb6p7ja212e64co56.png)

Now, I think it's time to add the outputbox where we will see the html output of our markdown source while we write.
To do that we are gonna add a HTMLLabel something like this inside `init_window` function:

```python
self.outputbox = HTMLLabel(self, width="1", background="white", html="<h1>Welcome</h1>")
self.outputbox.pack(fill=BOTH, expand=1, side=RIGHT)
self.outputbox.fit_height()
```
We use `HTMLLabel` from *tkhtmlview* with width of **1** (again) , we set the width to 1 because the window will be shared between Input Field and Output Box with ratio of **1:1** (You'll understand what I mean when you'll run the script).
`html` keyword argument stores the value which will be shown on first time. 
Then we pack it in the window with `side` as `RIGHT` to put this in right side of the Input Field.
and the `fit_height()` makes the texts fit inside the widget (As far as I think 😁)

Run the code.

![Output Box Added!](https://thepracticaldev.s3.amazonaws.com/i/emdqws7pfiudvw7ltoo9.png)

Now if you start writing in the input field. you may be disappointed (Don't be!) to see that the output is not getting updated as we type in, because we have not told your program to do so.

To do that we are gonna first bind a event with our editor, whenever the text is modified , the output will be updated, something like this:

```python
self.inputeditor.bind("<<Modified>>", self.onInputChange)
```
> Put that line inside the init_window() function

So basically that line tells the `inputeditor` to call the `onInputChange` function whenever the text is changed. But as we don't have that function yet, we are gonna write it.

```python
...
def onInputChange(self , event):
    self.inputeditor.edit_modified(0)
    md2html = Markdown()
    self.outputbox.set_html(md2html.convert(self.inputeditor.get("1.0" , END)))
```    
In the first line using `edit_modified(0)` we reset the Modified flag so that it can be reused otherwise after the first event call it will not work anymore.
Next we create a Markdown object named md2html and on the last line, where first we.... wait! the last line maybe confusing for some readers. So let me it break it down into three lines.

```python
markdownText = self.inputeditor.get("1.0" , END)
html = md2html.convert(markdownText)
self.outputbox.set_html(html)
```
Here in the first line we fetch the markdown text from top to bottom of the input field. First argument of `self.inputeditor.get` tell to start scanning from the first line's 0th character (1.0 => [LINE_NUMBER].[CHARACTER_NUMBER]) , and the last argument tells to stop scanning when reached end.

Then we convert the scanned markdown text to html using `md2html.convert()` function and store it in `html` variable.

Finally we tell outputbox to display the output using the `.set_html()` function!

Run the script! You will see a functioning (almost) markdown editor. As you type in the input field, the output will also be updated!

But... Our Work is not yet finished. Users need to atleast open and save their texts.

To do that, we are gonna add a `File` menu in the menubar , where users will be able to Open and Save Files as well as Quit from the application!

In the `init_window` function we will add these:

```python
self.mainmenu = Menu(self)
self.filemenu = Menu(self.mainmenu)
self.filemenu.add_command(label="Open", command=self.openfile)
self.filemenu.add_command(label="Save as", command=self.savefile)
self.filemenu.add_separator()
self.filemenu.add_command(label="Exit", command=self.quit)
self.mainmenu.add_cascade(label="File", menu=self.filemenu)
self.master.config(menu=self.mainmenu)
```
I'll make this quick. Here we define a new Menu with Frame as its parent. Next, We define another Menu and previous menu as its parent. It will serve as our `File` menu. Then we add 3 sub menus (Open , Save as , Exit) and a separator using the `add_command()` and `add_separator()` functions. The **Open** sub-menu will execute the `openfile` function , the **Save as** sub-menu will execute `savefile` function. and finally the **Exit** will execute a builtin function `quit` which will close the program.

Then using the `add_cascade()` function we tell the first Menu object to include the `filemenu` variable which includes all our sub-menus with the label `File`. At last we use `self.master.config()` to tell our window to use `mainmenu` as our window's menubar.

![Menu Added](https://thepracticaldev.s3.amazonaws.com/i/geyhpbgqwkhmthh09dbi.png)
> It will look something like this, but don't yet run it. You'll get errors saying `openfile` & `savefile` functions aren't defined!

As you can understand now, we have to define two functions inside Window class , where we will use tkinter's filedialog.

First Let's define the function to open files

```python
def openfile(self):
    openfilename = filedialog.askopenfilename(filetypes=(("Markdown File", "*.md , *.mdown , *.markdown"),
                                                                  ("Text File", "*.txt"), 
                                                                  ("All Files", "*.*")))
    if openfilename:
        try:
            self.inputeditor.delete(1.0, END)
            self.inputeditor.insert(END , open(openfilename).read())
        except:
            print("Cannot Open File!")     
```
Here, at first we show the user a file browser dialog to choose a file to open using `filedialog.askopenfilename()`. With `filetypes` keyword argument we tell the dialog to only open these types of files by passing a tuple with supported files (basically all types of file) : Markdown files with `.md` , `.mdown` , `.markdown` extensions, Text Files with `.txt` extension, and in the next row using a wildcard extension, we tell the dialog to open files with any extension. 

Then we check if user has selected a file or not , if yes, we try to open the file. Then we delete all the text inside the input field from first line's 0th character to the END of the field, next we open and read the content of the selected file and insert the contents in the input field.
If our program can't open a file it will print out the error. But wait , that's not a good way to handle errors, we can do here is show an Error Message to the user something like this:

![Showing an Error Message](https://thepracticaldev.s3.amazonaws.com/i/oyq79guh9o4x6c3p9oln.png)

To do that, we are gonna first import `messagebox` from tkinter  package.
```python
from tkinter import messagebox as mbox
```
then instead of just printing a error message like we did above, we're gonna replace that line with the below line to show a proper error message to the user.

```python

mbox.showerror("Error Opening Selected File" , "Oops!, The file you selected : {} can not be opened!".format(openfilename))
```
This will create an Error message like the above screenshot I showed you, when the file can not be opened. In the `mbox.showerror` function , first argument is the title of the messagebox, and second one is the message to be displayed.

Now, We need to  write a `savefile` function to save our markdown input.

```python
def savefile(self):
        filedata = self.inputeditor.get("1.0" , END)
        savefilename = filedialog.asksaveasfilename(filetypes = (("Markdown File", "*.md"),
                                                                  ("Text File", "*.txt")) , title="Save Markdown File")
        if savefilename:
            try:
                f = open(savefilename , "w")
                f.write(filedata)
            except:
                mbox.showerror("Error Saving File" , "Oops!, The File : {} can not be saved!".format(savefilename))

```                
Here at first we scan all the content of the input field and store it in a variable, then ask user for the filename to save the contents in with giving option for two types of file types (.md and .txt)

If the user chooses a filename we try to save the contents of the input field stored in the variable `filedata`. If a exception is occurred, we show user a error message stating that the program wasn't able to save the file.

Don't Forget to test your application to check for any bugs! If you and I haven't made any mistakes our programs should run perfectly and look something like this, 

![Final Product](https://thepracticaldev.s3.amazonaws.com/i/i5gjc5mzhaeuvpst9jnr.png)
> **Full Source of this 'tdown' markdown editor is available at [GitHub](https://github.com/bauripalash/tdown) and also at [Repl.it](https://repl.it/@bauripalash/tdownlive) where you can test the editor on your browser!**

![We Finally Did it!](https://media1.tenor.com/images/86bfdafc0ec6509e13b1c1748564b2e6/tenor.gif?itemid=9101932)

If you get into any problems regarding this article you can let me know in the comments or DM me on twitter at [@bauripalash](https://twitter.com/bauripalash).

#### Some Notes:
* First, Remember that, this is just a toy editor. If you want to build more powerful editor you can use any other GUI libraries such as wxPython, PyQT , Kivy and many more which atleast has better html support ([Full List](https://wiki.python.org/moin/GuiProgramming)).

* In this article I only showed how to build a *basic* editor, you can also add many more cool features of your own such as Exporting as HTML or PDF, Adding buttons to simplify writing Markdown... etc etc.

* The HTML Rendering modules tkhtmlview or tk_html_widgets are not fully stable and only supports some basic html functionalities, so don't except much.


So... I hope you enjoyed this article and learnt some new things. Don't Forget to let me know if you are stuck somewhere or can't understand something. Last but not Least, Please Let me know if I did any mistakes above and your ideas or suggestions via comments or DM.

Thank You. 👻