As I have mentioned on different occasions, I like to read technical topics in order to review and learn new things. This weekend I decided to spend some time with Tkinter.
For starters Tk is a free and open-source, cross-platform widget toolkit that provides a library of basic elements of GUI widgets for building a graphical user interface (GUI) in many programming languages.
Tk provides a number of widgets commonly needed to develop desktop applications, such as button, menu, canvas, text, frame, label, etc. Tk has been ported to run on most flavors of Linux, Mac OS, UNIX, and Microsoft Windows.
Tk was designed to be extended, and a wide range of extensions are available that offer new widgets or other capabilities.
Since Tcl/Tk 8, it offers “native look and feel” (for instance, menus and buttons are displayed in the manner of “native” software for any given platform).
Tcl is a high-level, general-purpose, interpreted, dynamic programming language. It was designed with the goal of being very simple but powerful. Tcl casts everything into the mold of a command, even programming constructs like variable assignment and procedure definition. Tcl supports multiple programming paradigms, including object-oriented, imperative and functional programming or procedural styles.
Tcl interpreters are available for many operating systems, allowing Tcl code to run on a wide variety of systems. Because Tcl is a very compact language, it is used in embedded systems platforms.
The popular combination of Tcl with the Tk extension is referred to as Tcl/Tk, and enables building a graphical user interface (GUI) natively in Tcl. Tcl/Tk is included in the standard Python installation in the form of Tkinter.
Tkinter is a Python binding to the Tk GUI toolkit. It is the standard Python interface to the Tk GUI toolkit, and is Python’s de facto standard GUI.
The name Tkinter comes from Tk interface. As with most other modern Tk bindings, Tkinter is implemented as a Python wrapper around a complete Tcl interpreter embedded in the Python interpreter. Tkinter calls are translated into Tcl commands which are fed to this embedded interpreter, thus making it possible to mix Python and Tcl in a single application.
At work and for personal stuff, I always like to spend time reading / refreshing before jumping into the project at hand. In this case, I used Chrome to search for articles and videos. I then went on referencing some books that I have read and keep as a reference.
I ran into different YouTube videos that describe the implementation of a simple calculator in Python. Most of them were very similar down to the actual code. I am a firm believer to always “stand on the shoulders of giants” and “not reinventing the wheel”. In this case, most of the videos and tutorials seem to have been based on chapter 03 of the book “Python and Tkinter Programming” January 2000 (today’s date is January 22, 2017) by John E Grayson. If interested, I checked and the book is still available on Amazon.
My code in Python which was developed using PTVS (Python Tools for Visual Studio) in Visual Studio Professional 2013 and Spyder IDEs follows:
from tkinter import *
# **** define a window ****
def frame(root, side):
w = Frame(root, borderwidth=4, bd=4, bg=”powder blue”)
w.pack(side=side, expand=YES, fill=BOTH)
return w
# **** define a button ****
def button(root, side, text, command=None):
w = Button(root, text=text, command=command)
w.pack(side=side, expand=YES, fill=BOTH)
return w
# **** printf() ****
import sys
def printf(format, *args):
sys.stdout.write(format % args)
# **** ****
class Calculator(Frame):
# **** ****
def __init__(self):
# **** calculator window ****
Frame.__init__(self)
self.option_add(‘*Font’, ‘arial 16 bold’)
self.pack(expand=YES, fill=BOTH)
self.master.title(‘Simple Calculator’)
self.master.iconname(“calc1”)
# **** display for calculator (FLAT, GROOVE, RAISED, RIDGE, SUNKEN) ****
display = StringVar()
Entry(self, relief=RIDGE, textvariable=display, justify=’right’, bd=16, bg=’powder blue’).pack(side=TOP, expand=YES, fill=BOTH)
# **** create and display digits, minus and 0 buttons ****
for key in (“123”, “456”, “789”, “-0.”):
keyF = frame(self, TOP)
for char in key:
button(keyF, LEFT, char,
lambda w=display, c=char: w.set(w.get() + c))
# **** create and display operator (i.e., +, -, *) buttons ****
opsF = frame(self, TOP)
for char in “+-*/=”:
if char == ‘=’:
btn = button(opsF, LEFT, char)
btn.bind(‘<ButtonRelease-1>’,
lambda e, s=self, w=display: s.calc(w), ‘+’)
else:
btn = button(opsF, LEFT, char,
lambda w=display, s=char: w.set(w.get()+s))
# **** create and place CLEAR button (includes anonymous function to clear display) ****
clearF = frame(self, BOTTOM)
button(clearF, LEFT, ‘CLEAR’,
lambda w=display: w.set(”))
# **** carry out geometry management and redraw widgets if necessary, without calling any callbacks ****
self.master.update_idletasks()
# **** test code ****
windowWidth = self.master.winfo_width()
windowHeight = self.master.winfo_height()
print(” windowWidth:”, windowWidth)
print(“windowHeight:”, windowHeight)
# **** set the minimum size of the calculator ****
self.master.after_idle(lambda: self.master.minsize(self.master.winfo_width(), self.master.winfo_height()))
# **** ****
def OnBtnClick(w, s):
return
# **** ****
def calc(self, display):
# **** test code ****
op = display.get()
print(“op:”, op)
# **** ****
try:
display.set(eval(display.get()))
# **** test code ****
r = display.get()
print(” r:”, r)
except:
display.set(“ERROR”)
# **** run calculator ****
if __name__ == ‘__main__’:
# **** test code for printf() ****
i = 123
val = 1.23
s = “Hello, World!!!”
printf(“s ==>%s<== i: %6d val: %6.2f\n”, s, i, val)
# **** test code ****
s = “abc”
for char in s:
printf(“char: %c type(char): %s\n”, char, type(char))
# **** test code ****
char = ‘2’
s = ‘ %s ‘%char
printf(“s ==>%s<==\n”, s)
# **** ****
Calculator().mainloop()
By the way, I decided to use two IDEs to see if it made a difference on the development experience. Python is not C# ;o) In this case both choices performed equally well :o)
The printf() function is there to simplify development and testing by printing some values. It is not needed by the calculator application.
You can also see several segments in the code appropriately labeled “test code”. I just added them to illustrate what happens at different stages in the calculator.
I enhanced the original calculator code by not allowing the display to shrink past the initial value. That way the calculator becomes easier to use. The additional code does not affect the basic operation of the calculator.
The code uses lambda expressions. If you are interested, take a look at “Anonymous Functions” in chapter 5 “First-Class Functions” in the book “Fluent Python” by Luciano Ramalho. His [edited] advice: “Outside the limited context of arguments to higher-order functions [as is the case in the Calculator code], anonymous functions are rarely useful in Python”. I happened to have a copy of the book. Last year Luciano Ramalho was a guest on a webinar of the ACM (Association for Computing Machinery) which I watched live.
Yesterday I was going to enhance operation of the code, by clearing the calculator display when the first digit is entered after an ERROR is displayed. In the current software, after the word “ERROR” is displayed, you need to manually clear it or the following input is appended to the ERROR sting (e.g., “ERROR123…”). I had to make a deep dish pizza from scratch (dough included) for lunch, so I had to cut it short.
On a completely separate subject, it used to be that LeetCode weekly contents took place in the evening. I am a morning person so my wife and I head to bed between 08:00 PM and 08:30 PM. I was pleasantly surprised this morning when I received an automated message with the following (edited for typo) text snippet:
“Our contests start on Saturday at 7:30am PDT and 6:30pm PDT. You will have 1.5 hours to solve 3 interesting problems”.
The first one starts at 09:30 AM CST which is more than adequate for me. I tend to get up around 05:00 AM. Hopefully there will be a contest next week early morning and I will be available to give it a try.
If you have comments or questions regarding this post of any other entry in the blog, please do not hesitate and send me a message via email. I will not use your name unless you explicitly tell me to do so.
John
john.canessa@gmail.com
Follow me on Twitter: @john_canessa