Posts: 21
Threads: 5
Joined: Sep 2024
Hello, Python Community
I'm working on a calculator with tkinter. Actually, I'm learning Python to be able to teach to a friend of mine, I'm a (.Net C#) programmer, she's not.
So I created 10 buttons for the digits, and their respective methods which write each digit in the textbox (Entry). Buuuuut ... I had the curious idea of sizing the buttons (in pixels), and searched the web, and found out that I had to add an (invisible 1x1) PhotoImage to the buttons to be able to size them in pixels (those are matters of Python I don't understand/like, that it is too elaborate in some aspects), because, as you may imagine, in the GUI calculator buttons need to be of a customized size. Well, it worked, but on the button I do this, then the associated method does not work any more, and I can't absolutely figure out why. That is, in my code button0 looks like desired, but does not print the 0 character in the textbox.
I will appreciate any help for solving this issue.
I attach the code by now.
Thank you!
Pablo
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
global entry
def __init__(self):
global entry
super().__init__()
self.geometry("400x600+100+100")
self.title("Calculadora Científica Python")
entry = ttk.Entry(width=300, font=('Courier New', 20))
entry.place(x=10, y=10)
pixel = tk.PhotoImage(width=1, height=1)
button0 = tk.Button(text='0', width=60, height=40, image=pixel, compound='c', command=self.button0Press)
button0.place(x=160, y=540)
button1 = tk.Button(text='1', command=self.button1Press)
button1.place(x=80, y=480)
button2 = tk.Button(text='2', command=self.button2Press)
button2.place(x=160, y=480)
button3 = tk.Button(text='3', command=self.button3Press)
button3.place(x=240, y=480)
button4 = tk.Button(text='4', command=self.button4Press)
button4.place(x=80, y=420)
button5 = tk.Button(text='5', command=self.button5Press)
button5.place(x=160, y=420)
button6 = tk.Button(text='6', command=self.button6Press)
button6.place(x=240, y=420)
button7 = tk.Button(text='7', command=self.button7Press)
button7.place(x=80, y=360)
button8 = tk.Button(text='8', command=self.button8Press)
button8.place(x=160, y=360)
button9 = tk.Button(text='9', command=self.button9Press)
button9.place(x=240, y=360)
def button0Press(self):
global entry
entry.insert(tk.END, '0')
def button1Press(self):
global entry
entry.insert(tk.END, '1')
def button2Press(self):
global entry
entry.insert(tk.END, '2')
def button3Press(self):
global entry
entry.insert(tk.END, '3')
def button4Press(self):
global entry
entry.insert(tk.END, '4')
def button5Press(self):
global entry
entry.insert(tk.END, '5')
def button6Press(self):
global entry
entry.insert(tk.END, '6')
def button7Press(self):
global entry
entry.insert(tk.END, '7')
def button8Press(self):
global entry
entry.insert(tk.END, '8')
def button9Press(self):
global entry
entry.insert(tk.END, '9')
MainWindow().mainloop()
Posts: 1,145
Threads: 114
Joined: Sep 2019
Dec-05-2024, 11:42 PM
(This post was last modified: Dec-05-2024, 11:42 PM by menator01.)
This works. On a side not it's better practice to not use globals. You created a class so
instead of entry and in the methods/functions global entry you can use self.entry and access the instance variable of self.
import tkinter as tk
root = tk.Tk()
root.geometry('400x200')
pixel = tk.PhotoImage(width=1, height=1)
btn = tk.Button(root, image=pixel, compound='center', text='Submit', width=74, height=10)
btn.pack()
btn2 = tk.Button(root, image=pixel, compound='center', text='Submit', width=124, height=20)
btn2.pack()
btn3 = tk.Button(root, image=pixel, compound='center', text='Submit', width=35, height=100)
btn3.pack()
root.mainloop() Another working example. Can use mouse or keypad numbers. esc will clear and either return will total
import tkinter as tk
from decimal import Decimal
class Window(tk.Tk):
''' Class creates the display window '''
def __init__(self):
super().__init__()
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.resizable(False, False)
self.title('Tkinter Basic Calculator')
# Create a textvariable for entry manipulation
self.var = tk.StringVar()
self.entry = tk.Entry(self, textvariable=self.var)
self.entry.grid(column=0, row=0, sticky='we', padx=5, pady=3)
self.entry.insert('end', '0')
# Sert focus on the entry widget
self.entry.focus()
# Create container for buttons
btn_container = tk.Frame(self)
btn_container.grid(column=0, row=1, sticky='news')
for i in range(3):
btn_container.grid_columnconfigure(i, weight=3, uniform='button')
# Create the dgits for button text
digits = [i for i in range(1, 10)]
# Empty list to hold buttons and some variables for columns and rows
buttons = []
i = 0
col, row = 0, 0
# 1x1 image
pixel = tk.PhotoImage(width=1, height=1)
# Keep a reference of image
pixel.backup = pixel # Note button will not work without this
# Create the buttons
for button in digits:
buttons.append(tk.Button(btn_container, text=button, width=74, height=15))
buttons[i]['image'] = pixel
buttons[i]['compound'] = 'left'
buttons[i]['cursor'] = 'hand2'
buttons[i]['command'] = lambda btn=buttons[i]['text']: self.pressed(btn)
buttons[i].grid(column=col, row=row, padx=2, pady=2)
# Increatment for each loop through
i += 1
# Set columns and rows
if col >= 2:
row += 1
col = 0
else:
col += 1
# Create the zeo button
zero = tk.Button(btn_container, text='0', image=pixel, compound='center')
zero.grid(column=0, row=3, sticky='we', padx=2, pady=2)
zero.configure(command=lambda val='0': self.pressed(val))
# Button to clear tthe label text
clear_btn = tk.Button(btn_container, text='Clear', width=74, height=15, compound='center', command=self.clear)
clear_btn.grid(column=0, columnspan=4, row=4, sticky='new', padx=2, pady=2)
clear_btn.configure(cursor='hand2', bg='tomato', activebackground='red', fg='white', activeforeground='white',
image=pixel)
# Create the decimal/period button
self.period = tk.Button(btn_container, text='.', anchor='s', width=74, height=15, image=pixel, compound='center')
self.period.grid(column=1, row=3, sticky='we', padx=2, pady=2, ipady=1)
self.period.configure(font=(None, 24), command=lambda val='.': self.pressed(val))
# Create the equal button
self.eq_btn = tk.Button(btn_container, text='=', width=74, height=15, image=pixel, compound='center')
self.eq_btn.grid(column=2, row=3, sticky='we', padx=2, pady=2)
self.eq_btn.configure(font=(None, 24), command=lambda: self.calculate())
# Create list of symbols
symb = ['x','/','+','-']
# Empty list to hold symbols buttons
symbols = []
for i in range(len(symb)):
button = tk.Button(btn_container, text=symb[i])
symbols.append(button)
symbols[i]['command'] = lambda symbol=button.cget('text'): self.pressed(symbol)
symbols[i].grid(column=3, row=i, sticky='we', padx=2, pady=2)
symbols[i].configure(font=(None, 18), width=74, height=15, image=pixel, compound='center')
# Bind some buttons
self.bind('<Return>', self.calculate)
self.bind('<KP_Enter>', self.calculate)
self.bind('<Escape>', self.clear)
# Using trace to manipulate the the entry field
self.var.trace('w', self.change)
# Check for valid input
valid = self.register(self.valid)
self.entry.configure(validate='key', validatecommand=(valid, '%S'))
def valid(self, arg):
''' Method for validating characters insert. We only want numbers
or calculation symbols '''
if arg == 'x':
return True
elif arg.isalpha() or arg == '=':
return False
else:
return True
def change(self, var, mode, index):
''' Method will replace * with x for the multiply symbol '''
var = self.var.get()
var = var.replace('*', 'x')
# If there is a leading zero replace with entry
if self.var.get()[:-1] == '0' and len(var) ==2:
var = self.var.get()[1:]
self.entry.delete(0, 'end')
self.entry.insert('end', var)
# method for the buttons
def pressed(self, btn):
''' Method places pressed button keys into the entry field '''
self.entry.insert('end', btn)
def calculate(self, event=None):
''' Method for calaculting results '''
values = self.entry.get()
# If x in the calculation replace with *
if 'x' in values:
values = values.replace('x', '*')
result = eval(values)
# If the results is a float and has only zero on the right side, normalize
if isinstance(result, float):
result = Decimal(result).normalize()
else:
result = result
# Clear entry field and insert results
self.entry.delete(0, 'end')
self.entry.insert('end', result)
# Clear the entry field
def clear(self, event=None):
self.entry.delete(0, 'end')
self.entry.insert('end', '0')
Window().mainloop()
Posts: 21
Threads: 5
Joined: Sep 2024
Dec-14-2024, 02:46 PM
(This post was last modified: Dec-14-2024, 04:24 PM by deanhystad.)
Hello,
Thank you for your help and code.
With only this
pixel.backup = pixel
my own code works fine.
But I learnt another things from your code.
Now I'm trying to split the Entry.get() string to get both operands and then do the calculation, but the split function is throwing errors that I can´t figure out what they are.
This is my Calculate function:
def Calculate(self):
expr = self.entry.get()
array = re.split('+ |- |x |/', expr)
str1 = array[0]
str2 = array[1]
if self.what == '+':
result = Decimal(str1) + Decimal(str2)
elif self.what == '-':
result = Decimal(str1) - Decimal(str2)
elif self.what == 'x':
result = Decimal(str1) * Decimal(str2)
elif self.what == '/':
result = Decimal(str1) / Decimal(str2)
self.entry.delete(0, tk.END)
self.entry.insert(tk.END, str(result))
self.oper = False
self.replace = True This is the error message:
Error: re.PatternError: nothing to repeat at position 0
(the Entry is justify=RIGHT)
Would you help me, once again?
Thank you in advance
Pablo
Posts: 6,812
Threads: 20
Joined: Feb 2020
Dec-14-2024, 04:34 PM
(This post was last modified: Dec-14-2024, 04:41 PM by deanhystad.)
You need to read the re documentation.
https://docs.python.org/3/library/re.html
"+" has special meaning in a regular expression. If you want to look for the "+" character, you need to precede with a backslash.
As long as you're doing some reading, you should also read about tkinter variables (StringVar, IntVar, DoubleVar, BoolVar). Using variables is easier than using insert().
https://www.geeksforgeeks.org/python-set...-variable/
And you can also read about python operators.
https://docs.python.org/3/library/operator.html
Posts: 21
Threads: 5
Joined: Sep 2024
Hello all who are helping me
What I was looking for before is:
array = re.split('[+ - x /]', expr) that gave me the 2 operands for the calculation.
Now I have another issue
buttonCalculate = self.CreateButton('=', pixel, self.buttonCalculatePress)
buttonCalculate.place(x=240, y=540)
radical = tk.PhotoImage(Image.open('radical.png'))
radical.backup = radical
buttonSqrt = self.CreateButtonSpec(radical, self.buttonSqRtPress)
buttonSqrt.place(x=80, y=300)
def CreateButton(self, what, pixel, cmd) -> tk.Button:
btn = tk.Button(text=what, font=['Arial', 16], width=60, height=40, image=pixel, compound='left', command=cmd)
return btn
def CreateButtonSpec(self, pic, cmd) -> tk.Button:
btn = tk.Button(width=60, height=40, image=pic, compound='center', command=cmd)
return btn Here the function CreateButton() works fine, but the function CreateButtonSpec() doesn't, I get an error on its first line, and (now) the UI doesn't even show. I'm trying to make a Button with an image (the 'radical.png' file is in the same folder than the python file)
Can you help me to fix this?
I appreciate your help
Thanks
Pablo
Posts: 6,812
Threads: 20
Joined: Feb 2020
Dec-17-2024, 11:16 AM
(This post was last modified: Dec-17-2024, 11:16 AM by deanhystad.)
Where is your error message? If you have an error, you should post the error message and the traceback. You should also post code than can be run by others to reproduce the error. This includes posting the imports. I don't know of you are using PhotoImage from tkinter or Photoimage from PIL.ImageTk.
If you want to use PIL to open an image file and convert to an image you can use in a button, do it like this:
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
image = ImageTk.PhotoImage(Image.open("true_img.png"))
tk.Button(root, image=image, compound="left").pack()
root.mainloop() All of your button creates are wrong. The first argument to Button() is the object in which the button will reside, as shown in the example above.
You currently get away with not doing this because all of your widgets reside in the root widget. May as well start doing thing correctly.
I don't understand what self is.
def CreateButton(self, what, pixel, cmd) -> tk.Button:
btn = tk.Button(text=what, font=['Arial', 16], width=60, height=40, image=pixel, compound='left', command=cmd)
return btn Normally "self" is the first argument to an instance method in a class, but you don't define a class. I can tell this by the indentation. A class method cannot start on the left margin (no indent). So what is self?
Setting button sizes and using place() are bad practices. You should use a layout manager like pack() or grid(). Let a layout manager set the button sizes and alignments.
Posts: 21
Threads: 5
Joined: Sep 2024
Hello
I solved the image issue with using ImageTk instead of tk.
Now I have another, very strange issue, when I press the ln x button, I see in the debugger the correct result, but it doesn't get written on the Entry widget.
I attach all the code by now, you can find the issue part of the code at the very below.
I suppose it's a silly issue but can't figure out anything.
Thank you very much.
Pablo
import tkinter as tk
from decimal import Decimal
import re
from PIL import ImageTk, Image
import math
class MainWindow(tk.Tk):
point = False
oper = False
entry = None
what = None
replace = False
def __init__(self):
super().__init__()
self.geometry("400x600+100+100")
self.title("Calculadora Científica Python")
self.resizable(width=False, height=False)
self.entry = tk.Entry(width=28, font=('Courier New', 16), justify=tk.RIGHT)
self.entry.place(x=10, y=10)
pixel = tk.PhotoImage(width=1, height=1)
pixel.backup = pixel # Note button will not work without this
button0 = self.CreateButton('0', pixel, self.button0Press)
button0.place(x=160, y=540)
button1 = self.CreateButton('1', pixel, self.button1Press)
button1.place(x=80, y=480)
button2 = self.CreateButton('2', pixel, self.button2Press)
button2.place(x=160, y=480)
button3 = self.CreateButton('3', pixel, self.button3Press)
button3.place(x=240, y=480)
button4 = self.CreateButton('4', pixel, self.button4Press)
button4.place(x=80, y=420)
button5 = self.CreateButton('5', pixel, self.button5Press)
button5.place(x=160, y=420)
button6 = self.CreateButton('6', pixel, self.button6Press)
button6.place(x=240, y=420)
button7 = self.CreateButton('7', pixel, self.button7Press)
button7.place(x=80, y=360)
button8 = self.CreateButton('8', pixel, self.button8Press)
button8.place(x=160, y=360)
button9 = self.CreateButton('9', pixel, self.button9Press)
button9.place(x=240, y=360)
buttonPlus = self.CreateButton('+', pixel, self.buttonPlusPress)
buttonPlus.place(x=320, y=360)
buttonMinus = self.CreateButton('-', pixel, self.buttonMinusPress)
buttonMinus.place(x=320, y=420)
buttonMulBy = self.CreateButton('x', pixel, self.buttonMulByPress)
buttonMulBy.place(x=320, y=480)
buttonDivBy = self.CreateButton('/', pixel, self.buttonDivByPress)
buttonDivBy.place(x=320, y=540)
buttonPoint = self.CreateButton('.', pixel, self.buttonPointPress)
buttonPoint.place(x=80, y=540)
buttonCalculate = self.CreateButton('=', pixel, self.buttonCalculatePress)
buttonCalculate.place(x=240, y=540)
radical = ImageTk.PhotoImage(Image.open('radical.png'))
radical.backup = radical
buttonSqrt = self.CreateButtonSpec(radical, self.buttonSqRtPress)
buttonSqrt.place(x=80, y=300)
buttonLnx = self.CreateButton('ln x', pixel, self.buttonLnxPress)
buttonLnx.place(x=160, y=300)
# Check for valid input
valid = self.register(self.valid)
self.entry.configure(validate='key', validatecommand=(valid, '%S'))
def valid(self, arg):
''' Method for validating characters insert. We only want numbers
or calculation symbols '''
if arg.isdigit():
return True
elif (arg == '+' or arg == '-' or arg == 'x' or arg == '/') and self.oper == False:
self.oper = True
self.point = False
elif arg == '.' and self.point == False:
self.point = True
return True
else:
return False
def CreateButton(self, what, pixel, cmd) -> tk.Button:
btn = tk.Button(self, text=what, font=['Arial', 16], width=60, height=40, image=pixel, compound='left', command=cmd)
return btn
def CreateButtonSpec(self, pic, cmd) -> tk.Button:
btn = tk.Button(self, width=60, height=40, image=pic, compound='left', command=cmd)
return btn
def PressButton(self, what):
if self.replace == True:
self.entry.delete(0, tk.END)
self.replace = False
self.entry.insert(tk.END, what)
def button0Press(self):
self.PressButton('0')
def button1Press(self):
self.PressButton('1')
def button2Press(self):
self.PressButton('2')
def button3Press(self):
self.PressButton('3')
def button4Press(self):
self.PressButton('4')
def button5Press(self):
self.PressButton('5')
def button6Press(self):
self.PressButton('6')
def button7Press(self):
self.PressButton('7')
def button8Press(self):
self.PressButton('8')
def button9Press(self):
self.PressButton('9')
def buttonPlusPress(self):
if self.oper == False:
self.entry.insert(tk.END, '+')
self.what = '+'
self.oper = True
def buttonMinusPress(self):
if self.oper == False:
self.entry.insert(tk.END, '-')
self.what = '-'
self.oper = True
def buttonMulByPress(self):
if self.oper == False:
self.entry.insert(tk.END, 'x')
self.what = 'x'
self.oper = True
def buttonDivByPress(self):
if self.oper == False:
self.entry.insert(tk.END, '/')
self.what = '/'
self.oper = True
def buttonPointPress(self):
if self.point == False:
self.entry.insert(tk.END, '.')
self.point = True
def buttonCalculatePress(self):
self.Calculate()
def Calculate(self):
expr = self.entry.get()
array = re.split('[+ - x /]', expr)
if (len(array) == 1):
self.point = False
return
str1 = array[0]
str2 = array[1]
if self.what == '+':
result = Decimal(str1) + Decimal(str2)
elif self.what == '-':
result = Decimal(str1) - Decimal(str2)
elif self.what == 'x':
result = Decimal(str1) * Decimal(str2)
elif self.what == '/':
result = Decimal(str1) / Decimal(str2)
self.entry.delete(0, tk.END)
self.entry.insert(tk.END, str(result))
self.oper = False
self.replace = True
def buttonSqRtPress(self):
self.Calculate()
arg = Decimal(self.entry.get())
result = math.sqrt(arg)
self.entry.delete(0, tk.END)
self.entry.insert(tk.END, str(result))
def buttonLnxPress(self):
self.Calculate()
arg = Decimal(self.entry.get())
result = math.log(arg)
self.entry.delete(0, tk.END)
self.entry.insert(tk.END, str(result))
MainWindow().mainloop()
Posts: 6,812
Threads: 20
Joined: Feb 2020
Dec-22-2024, 01:10 AM
(This post was last modified: Dec-30-2024, 07:33 PM by deanhystad.)
Your valid() function is incorrect. Validators are tricky. Comment out line 71 until you learn enough to write one correctly.
I would use a Label instead of an Entry. If you want to support keyboard entry, bind the key down event to a function that calls your button press functions. This eliminates the need for a validator, which, as I said, is tricky to write.
You can replace all your button press functions with one function that takes an argument.
import tkinter as tk
from decimal import Decimal
import re
import math
import operator
class Calculator(tk.Tk):
"""Calculator application."""
font = ['Arial', 16]
small_font = ['Arial', 12]
large_font = ['Arial', 18]
digits = 16
def __init__(self):
"""Initialize instance."""
def button(label, row, column, key=None):
"""Make a button. Position in row, column."""
if key is None:
key = label[0]
font = self.small_font if len(label) > 1 else self.font
button = tk.Button(self, text=label, font=font, command=lambda: self._button_press(key))
button.grid(row=row, column=column, sticky="news")
self._buttons[key] = button
super().__init__()
self._point = False
self._operator = None
self._replace = False
self._operators = {
'+': operator.add,
'-': operator.sub,
'x': operator.mul,
'/': operator.truediv
}
self.bind('<KeyPress>', self._key_press)
self._display = tk.Label(width=self.digits, anchor="e", font=self.large_font, background="black", foreground="white")
self._display.grid(row=0, column=0, columnspan=4)
self._buttons = {}
button('ln', 1, 0)
button('sqrt', 1, 1)
button('1', 2, 0)
button('2', 2, 1)
button('3', 2, 2)
button('+', 2, 3)
button('4', 3, 0)
button('5', 3, 1)
button('6', 3, 2)
button('-', 3, 3)
button('7', 4, 0)
button('8', 4, 1)
button('9', 4, 2)
button('x', 4, 3)
button('0', 5, 0)
button('.', 5, 1)
button('=', 5, 2)
button('/', 5, 3)
for row in range(1, 6):
self.grid_rowconfigure(row, weight=1, uniform=True)
for column in range(4):
self.grid_columnconfigure(column, weight=1, uniform=True)
def _key_press(self, event):
"""Called when key is pressed."""
self._button_press(event.char)
def _button_press(self, key):
"""Process button press."""
if key in "0123456789":
self.display(key)
elif key == ".":
if not self._point:
self._point = True
self.display(key)
elif key in self._operators:
if self._operator is None:
self._operator = key
self._point = False
self.display(key)
elif key == "=":
self.calculate()
elif key == "s":
self.display(math.sqrt(self.calculate()))
elif key == "l":
self.display(math.log(self.calculate()))
def calculate(self):
array = re.split('[+ - x /]', self._display["text"])
if (len(array) == 1):
result = Decimal(array[0])
else:
result = self._operators[self._operator](Decimal(array[0]), Decimal(array[1]))
self.display(result)
self._point = False
self._operator = None
return result
def display(self, value):
"""Update the display."""
if isinstance(value, str):
if self._replace:
self._display["text"] = value
self._replace = False
else:
self._display["text"] = self._display["text"] + value
else:
self._display["text"] = str(value)[:self.digits]
self._replace = True
Calculator().mainloop()
|