I am somewhat new to Python, and extremely new to class structures. I am trying to build a calculator GUI with buttons that insert a value into an Entry display. My calculator works without using class structures (the functional code is below), but I'm trying to make the same code work while incorporating the classes. My reason for doing this is to add a "shift" function that changes the values of text on the buttons, and what values (same as the text) they insert into the display. The values as you can see below are class variables, and in order to change the text on the buttons, the shift button is pressed which changes the value of the variables (I have gotten this to work in a non-tkinter file without buttons, etc). The problem is I am getting the GUI with an Entry and two Buttons (good) and '1' in the Entry. (not good) The buttons also don't do anything. Here is my code: (The one that works first)
from tkinter import *
import parser
import math
root = Tk()
root.title('Calculator')
displayb = Entry(root)
displayb.grid(row = 3, columnspan = 6, ipady = 7, ipadx = 100)
def display(e):
global i
i = len(displayb.get())
displayb.insert(i,e)
one = Button(root, text = '1', width = 10, height = 1, command = lambda : display(1))
one.grid(row = 4, column = 0)
root.mainloop()
New Code:
from tkinter import *
import parser
class Calc:
text1 = '1'
shft = 'shft1'
def __init__(self,master):
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.one = Button(master, text = Calc.text1, command = self.display((Calc.text1)))
self.one.grid(row = 4, column = 1)
self.update = Button(master, text = Calc.shft, command = self.update((Calc.shft[4:5])))
self.update.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
Calc.text1 = 'A'
Calc.shft = 'shft2'
else:
Calc.text1 = '1'
Calc.shft = 'shft1'
return
def display(self,e):
i = len(self.displaya.get())
self.displaya.insert(i,e)
return
root = Tk()
calc = Calc(root)
root.mainloop()
Can you help me find a solution that makes my code work properly? Thanks
Edit: I attempted using StringVar() and .set, but on the interface, the shift button, instead of saying shft1 or even shft2, said "PY_VAR1". The one button said 1, but inserted "PY_VAR0" to the display. When pressed, the shift button raised this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1699, in __call__
return self.func(*args)
File "/Users/ryanflynn/Classesmod.py", line 13, in <lambda>
self.update_button = Button(master, text = self.shft, command = lambda: self.update(int(self.shft[4:5]))) # use int()
TypeError: 'StringVar' object is not subscriptable
Your code has few errors in it, here is a list of all of them and a modified version of your code:
1st: Your button has the same name as one of your function, change it to a unique name.
2nd: Don't use the class's namespace unless it's necessary, use self instead.
3rd: You only need one set of brackets when calling a function, else it's redundant.
4th: Use lambda or functools.partial to set a command that has arguments.
5th: You can use END(since you did from tkinter import *, otherwise tkinter.END) to specify the ending instead of doing len(self.displaya.get()).
6th: Calc.shft[4:5] returns a string not an int. So in order to get your result, use int(Calc.shft[4:5]).
7th: I didn't see you using parser, so I don't see why you need it.
8th: Don't add a return at the end of a function that returns nothing, it's redundant. Since that's the default for all functions.
This is a modified(working) version of your code:
from tkinter import *
class Calc:
def __init__(self,master):
self.text1 = '1' # make them self's not Calc's
self.shft = 'shft1'
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.one = Button(master, text = self.text1, command = lambda: self.display(self.text1)) # use lambda and single brackets
self.one.grid(row = 4, column = 1)
self.update_button = Button(master, text = self.shft, command = lambda: self.update(int(self.shft[4:5]))) # use int()
self.update_button.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
self.text1 = 'A'
self.shft = 'shft2'
else:
self.text1 = '1'
self.shft = 'shft1'
def display(self,e):
self.displaya.insert(END,e) # use END instead
root = Tk()
calc = Calc(root)
root.mainloop()
This code does work but the button text shouldn't change
EDIT:
If you want to use string variable to make the text change, you will need a StringVar as well:
from tkinter import *
class Calc:
def __init__(self,master):
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.text1 = StringVar(value = '1') # change to this
self.shft = StringVar(value = 'shft1')
self.one = Button(master, textvariable = self.text1, command = lambda: self.display(self.text1.get())) # Use .get()
self.one.grid(row = 4, column = 1)
self.update_button = Button(master, textvariable = self.shft, command = lambda: self.update(int(self.shft.get()[4:5])))
self.update_button.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
self.text1.set('A') # set the variable
self.shft.set('shft2')
else:
self.text1.set('1')
self.shft.set('shft1')
def display(self,e):
self.displaya.insert(END,e) # use END instead
root = Tk()
calc = Calc(root)
root.mainloop()
When you provide a function to the command argument, you need to provide the actual function, and not the result of calling the function. In other words, you can't have () on the end. One hacky way of handling this is to use lambda as you did earlier:
self.one = Button(master, text = Calc.text1, command = lambda: self.display((Calc.text1)))
A slightly less hacky way is to use functools.partial:
from functools import partial
# ...
self.one = Button(master, text = Calc.text1, command = partial(self.display, Calc.text1))
IMO the best way is to make a real method to use.
self.one = Button(master, text = Calc.text1, command = self.one_pushed)
# ...
def one_pushed(self):
self.display(Calc.text1)
As a side note: tkinter will automatically calculate the end point for you if you pass 'end' as the position:
def display(self,e):
self.displaya.insert('end',e)
Related
I have created a class within tkinter that builds a frame and buttons (example below).
I am also trying to create a series of radiobuttons each with a command, and rather than having to type three times the same, I am trying to use a dictionary that stores the function name and the function (again see below):
import tkinter as tk
from tkinter import ttk
from tkinter import *
class EnoxDoseCalculator:
def __init__(self, root):
root.title('Enoxaparin Dose Calculator')
indication = {'VTE': self.vte, 'Atrial Fibrilation': self.af, 'Mechanical Heart valve': self.mech}
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0, sticky='nsew')
test_button = ttk.Button(mainframe, text= 'Push Mew!!', command = self.format)
test_button.grid(row = 0, column = 0)
def vte(self):
pass
def af(self):
pass
def mech(self):
pass
def format(self):
self.var3 = tk.StringVar()
for key, value in self.indication.items():
ttk.Radiobutton(mainframe, text = self.indication.key, variable = self.var3, value = self.indication.key, command = self.indication.value).grid(row = n+1, column =0, sticky = 'nswe')
root = Tk()
EnoxDoseCalculator(root)
print(dir(EnoxDoseCalculator))
root.mainloop()
However, I keep getting this message:
I get this is to do with the dictionary functions being listed before they are created, so my question is, where do I place said dictionary, because I am sure this is possible. or is it that I am using the wrong names for the function?
Try this:
class EnoxDoseCalculator:
def __init__(self, root):
self.root = root # add this (mostly for consistency/best practice)
self.root.title('Enoxaparin Dose Calculator')
self.indication = {'VTE': self.vte, 'Atrial Fibrilation': self.af, 'Mechanical Heart valve': self.mech}
self.mainframe = ttk.Frame(self.root) # update this to include 'self'
self.mainframe.grid(column=0, row=0, sticky='nsew')
# update this to include 'self'
self.test_button = ttk.Button(self.mainframe, text='Push Mew!!', command=self.format)
self.test_button.grid(row=0, column=0)
Using self to namespace your class variables to EnoxDoseCalculator
I am currently writing a script in python that takes in user data at the beginning of the script which may need to be updated later.
The initial user data is input through a tkinter window which is then passed along to the lower functions. Later in the script, if the information is detected to be bad, I want to alert the user that the info was bad and prompt them to re-input the data without having to start the program from the beginning.
I was attempting to achieve this by adding in a sub window function that would be called whenever the data needed to be re-input, take the new user input, and then pass it up back up to the function that called it. The code below roughly shows what I'm trying to do:
import tkinter as tk
from tkinter import *
def gui():
window = tk.Tk()
window.geometry('300x200')
L1 = tk.Label(window, text = 'This is a test')
L1.grid(column = 1, row = 0)
L2 = tk.Label(window, text = 'Token')
L2.grid(column = 0, row = 1)
E1 = tk.Entry(window, width = 25)
E1.grid(column = 1, row = 1)
B1 = tk.ttk.Button(window, text = 'Run', command = lambda: shell(window, E1.get()))
B1.grid(column = 1, row = 2)
window.mainloop()
def shell(window, val):
print('Old Val:', val)
val = subwindow_test(window)
print('New Val:', val)
def subwindow_test(window):
def subwinfunc(window, val):
if val == None or val == '':
print('Enter something')
else:
window.sub_win.destroy()
return
window.sub_win = tk.Toplevel(window)
window.sub_win.geometry('300x200')
L1 = tk.Label(window.sub_win, text = 'this is a subwindow')
L1.grid(column = 1, row = 0)
L2 = tk.Label(window.sub_win, text = 'New Token')
L2.grid(column = 0, row = 1, sticky = 'E')
var = StringVar()
E1 = tk.Entry(window.sub_win, width = 25, textvariable = var)
E1.grid(column = 1, row = 1)
B1 = tk.ttk.Button(window.sub_win, text = 'Return', command = lambda: subwinfunc(window, var.get()))
B1.grid(column = 1, row = 2)
window.sub_win.mainloop()
return var.get()
gui()
The idea is to pass the window down to the subwindow_test function, spawn a sub window using tk.Toplevel, ask the user for new data, then destroy the sub window and pass the newly entered data back up to the calling function.
In theory, this would prevent me from having to restart the code from the beginning as this subwindow_test function could be run from anywhere in the code.
The issue is that after subwinfunc returns after destroying window.sub_win, the code hangs until the original window object (the one created in the gui function) is closed. Also, removing the return line from subwinfunc does not change this.
Is there a way to get around this issue?
I have tried using a separate window (An entirely different window, not a sub window of the one created in gui), but the same problem comes up.
It is also not possible, as far as I can tell, to pass the sub window object back up to the calling function and close it there, as subwindow_test cannot return until it breaks from window.sub_win.mainloop() (If the return comes before the mainloop(), the window will never appear) .
Additionally, the only way that I could find to get the value to return at all is to use a StringVar. I would rather try and avoid using global variables, and if I had to guess, I would say that the return val.get() is most likely the root of the problem. However because I can't find another way to pass variables up from this function, I'm stumped.
You should not be calling mainloop more than once. Tkinter provides the ability to wait for a window to be closed before continuing with the wait_window method.
Here is a very simple example that shows how to create a popup dialog that won't return until the user clicks the ok or cancel button.
def get_input():
value = None
def do_ok(event=None):
nonlocal value
value = entry.get()
top.destroy()
def do_cancel():
nonlocal value
value = None
top.destroy()
top = tk.Toplevel()
entry = tk.Entry(top)
ok = tk.Button(top, text="ok", command=do_ok)
cancel = tk.Button(top, text="cancel", command=do_cancel)
entry.bind("<Return>", do_ok)
entry.pack(side="top", fill="x")
ok.pack(side="right")
cancel.pack(side="left")
top.wait_window(top)
return value
I am trying to use lambda to create callbacks for tkinter buttons.
There are multiple buttons and each callback needs to pass an object inside it. Following code is what I am doing and is running fine
var0 = tk.StringVar()
label = tk.Label(top, bg = "White",height = 2, width = 12,textvariable=var0, justify="right")
def b0Callback(var):
var.set(var.get()+"0")
return
# creating a label which will print value of the any of the 0-9 button pressed
# creating a button 0
b0 = tk.Button(numFrame0, height = 1, width = 4, bg = "grey", text =
"0",command = lambda: b0Callback(var0))
#there are more buttons like that
var0 is used to update a label. Above code is working fine but I have to create callback for 0 to 9 and I have to just repeat above definition. So I tried using following example from this tutorial
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
mytripler = myfunc(3)
print(mydoubler(11))
print(mytripler(11))
Using it I did following
def Callback(n):
return lambda var.set(var.get()+n)
b0Callback = Callback("0")
This shows error invalid index in the return line at var.set
Is there any way to pass var0 in this case to avoid this error?
Maybe its only me, but I don't see a reason for using lambda if you just want to add a number to the label text.
Lets make a function for it that gets your StringVar() as a variable and adds some number to it:
def button_callback(str_var, number):
str_var.set(str_var.get() + str(number))
To run this command we simply put it in the code as a lambda function, otherwise it will run upon initialization (because we are providing a function instead of a reference).
So to run it within a button we declare it like this:
my_button = Button(root, text='Some text here', command=lambda: button_callback(my_string_var, 5))
The '5' could be potentially changed to any other number.
I have now solved the problem, here is the final code:
I have also changed the number of buttons to 300 and added code to arrange them all in a nice grid, just for fun. (You can change this to however many you want by changing for number in range(1, whatever).
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var0 = tk.StringVar()
self.var0.set('0')
# creating buttons and adding them to dictionary
self.buttons = {}
for number in range(1, 301):
self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})
label = tk.Label(self, textvariable=self.var0, font='none 50')
label.grid(column=0, row=0)
for button in self.buttons:
self.buttons[button].bind('<Button-1>', lambda event, num=button[6:]: self.Callback(event, num))
self.buttons[button].grid(column=(int(button[6:]) % 10), row=(int(button[6:]) / 10) + 1)
def Callback(self, event, num):
self.var0.set(num)
self.update()
I am trying to add the math.sqrt function in my py 2.7 calculator. Here is the function I am using:
from Tkinter import *
from math import *
class calculator:
def __init__(self, master):
master.title("Simple Calculator")
self.display = Entry(master)
self.display.grid(columnspan = 8, ipadx = 60, ipady = 10)
Button(master, text = "sqrt", fg = 'white', bg = 'black',
command = lambda:self.sqroot(), height =1, width = 10).grid(row = 6, column = 1)
def sqroot(self):
try:
self.s=self.display.get()
self.sqroot=sqrt(eval(self.s))
self.display.delete(0,END)
self.display.insert(0,self.sqroot)
except:
self.display.delete(0,END)
self.display.insert(0,'Invalid operation')
In the resulting GUI, I can use the 'sqrt' button correctly only once. I am getting the following error message from second time:
TypeError: 'float' object is not callable
calculator.sqroot is, initially, a function (the function that you want).
Then, for the instance of calculator that you're using, you change the function reference self.sqroot to a float reference self.sqroot=sqrt(eval(self.s)). When you try to call that float reference like it's a function, python gets exception-y.
So don't mask names that you've already used and you'll be fine.
You can get it work like this. The problem was because self.sqroot = sqrt(eval(self.s)) changes self.sqroot into a string. To the avoid the problem the code below just stores it in a temporary variable named result. Also note that you could also do something similar with self.s since its value is only needed temporarily inside the method.
Note I also modified your code so it conforms more to the PEP 8 - Style Guide for Python Code guidelines.
from Tkinter import *
from math import *
class calculator:
def __init__(self, master):
master.title("Simple Calculator")
self.display = Entry(master)
self.display.grid(columnspan=8, ipadx=60, ipady=10)
Button(master, text="sqrt", fg='white', bg='black',
command=lambda: self.sqroot(), height=1, width=10).grid(row=6, column=1)
def sqroot(self):
try:
self.s = self.display.get()
# self.sqroot = sqrt(eval(self.s))
result = sqrt(eval(self.s))
self.display.delete(0, END)
# self.display.insert(0, self.sqroot)
self.display.insert(0, result)
except:
self.display.delete(0, END)
self.display.insert(0, 'Invalid operation')
root = Tk()
calculator(root)
root.mainloop()
I am an amateur python programer with 2 months of experience. I am trying to write a GUI to-do list through tkinter. The actual placement of the buttons are not important. I can play around with those after. I need some help with displaying the appended item to the list. In the program, it updates well on the digit, but it won't print onto the list. I double checked it on the console and it says "tkinter.StringVar object at 0x102fa4048" but didn't update the actual list. What I need help is how can I update the list Main_Q on my the label column? Much appreciate some direction and coding help. Thanks.
Main_Q =["read","clean dishes", "wash car"]
from tkinter import*
root=Tk(className="total tasks in the Q")
#formula
def update():
global Main_Q
a=len(Main_Q)
num.set(a)
def add2list():
Main_Q.append(name)
a=len(Main_Q)
num.set(a)
print (Main_Q)
#output
num=StringVar()
y=Label(root, textvariable=num).grid(row=0, column=1)
#input
name=StringVar()
b=Entry(root, textvariable=name).grid(row=7,column=0)
#buttons
z=Button(root, text="update", command=update).grid(row=7, column=2)
add2list=Button(root,text="add", command=add2list).grid(row=7,
column=1)
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r = r + 1
root.mainloop()
Your problem is that your for loop which build up your labels doesnt get called after each time you have entered a new "task". To easily fix this you can move this loop into your update function.
If you want to prevent of looping through widget everytime you can create a new list with all widgets which already have been created:
createdWidgets = []
widgetsQueue = []
In your update function you than have to iterate through the widgetsQueue (widgetsQueue.pop() for instance), create the widgets and append the widget to the createdWidgetes list.
def update():
global Main_Q
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r += 1 # shorthand for r = r + 1
Some addition notes:
for the entry it is easier to seperate the definition and placement:
b = Entry(root)
b.grid(row=7,column=0)
because than Entry() returns its instance and you can use it to get the text:
b.get()
if you go shopping do you throw everything into one bag ?
from tkinter import *
does axactly that(in this case the globals() variable would be the bag).If you want to read more about that Importing Python Modules. To prevent that and shorten the amount of letters to type:
import tkinter as t # or tk
root = t.Tk()
*But for sure, if you just want a small program its okay.
Design:
To resolve your problem, you need to design this simple solution:
retrieve the text of the Tkinter.Entry widget using get() method.
add the text you got in 1 to Main_Q using append() method.
bind the button that updates on click both Main_Q and your GUI using command method.
create a new Tkinter.Label widget, set its text to the value you got in 1 and increment its corresponding row in the GUI.
I prefer to organize your code within a class that contains a constructor where Main_Q is initialized so that we call initialize_user_interface() to initialize the GUI with its three elements:
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.Main_Q = ["read", "clean dishes", "wash car"]
self.r = 0 # position of the row of each label
self.initialize_user_interface()
The method initialize_user_interface() does what its name says. We mainly bind the function update_gui() that inserts a new label with the text set to what the user types in Tkinter.Entry widget using command = self.update_gui
ef initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.Main_Q:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
Finally, nothing is simpler than update_gui() function:
def update_gui(self):
self.r+=1 # increment the row reserved to the new label
self.Main_Q.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
Programming the application:
Here is the full program:
'''
Created on Mar 11, 2016
#author: Bill BEGUERADJ
'''
import Tkinter
class Begueradj(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.main_queue = ["read", "clean dishes", "wash car"]
self.r = 0
self.initialize_user_interface()
def initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.main_queue:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
def update_gui(self):
self.r+=1
self.main_queue.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
def main():
root = Tkinter.Tk()
b = Begueradj(root)
root.mainloop()
if __name__ == "__main__":
main()
Demo:
Here is a screenshot of the running program:
Note:
I coded the previous program using Python 2.7, so if you want to test it, please change Tkinter to tkinter. Everything else remains the same.