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
Related
I want to write a program where after a user enters text and clicks a button, the text becomes a label and the button text is changed. My code is:
# Imports
import os, sys
import tkinter
"""
Tkinter program 1
text box + button + label
"""
# Button Entry
def enter(inputtedinfo, randvar, EnterMessage):
randvar = inputtedinfo.get()
EnterMessage = "Submitted!"
# Main Function
def main():
something = tkinter.Tk()
something.title("My First Tkinter Window")
something.geometry("600x400")
randvar = ""
EnterMessage = "Enter"
inputtedinfo = tkinter.StringVar()
userLabel = tkinter.Label(something, text = randvar)
userEntry = tkinter.Entry(something, textvariable = inputtedinfo)
userButton = tkinter.Button(something, text = EnterMessage, command = enter(inputtedinfo, randvar, EnterMessage))
userEntry.grid(row=0,column=0)
userLabel.grid(row=0,column=1)
userButton.grid(row=0,column=2)
something.mainloop()
sys.exit(0)
if(__name__ == "__main__"):
main()
The user input works, but clicking the button does nothing despite the fact that it is supposed to change the variables for the button and label displays. Did I mess up somewhere?
The command argument takes the name of a function. If you write the complete call with arguments, it's not the name of the function but whatever is returned by this exact function call. So, your button will not work. It will have the command None.
In order to do what you want to do, you have to make the StringVar()s accessible to the function you are calling. So, you can both get the contents of the entry and change the values of the button and the label. To do this, best add the string variables and the widgets as attributes to the toplevel you already created (something). So, they stay available to all functions and you can get and change information:
# Button Entry
def enter():
something.randvar.set(something.inputtedinfo.get())
something.userButton["text"] = "Submitted!"
# Main Function
def main():
global something
something = tkinter.Tk()
something.title("My First Tkinter Window")
something.geometry("600x400")
something.randvar = tkinter.StringVar()
something.randvar.set("")
EnterMessage = "Enter"
something.inputtedinfo = tkinter.StringVar()
userLabel = tkinter.Label(something, textvariable = something.randvar)
something.userEntry = tkinter.Entry(something, textvariable = something.inputtedinfo)
something.userButton = tkinter.Button(something, text = EnterMessage, command = enter)
something.userEntry.grid(row=0,column=0)
userLabel.grid(row=0,column=1)
something.userButton.grid(row=0,column=2)
something.mainloop()
if(__name__ == "__main__"):
main()
There are few issues in your code:
assign string to textvariable, should use StringVar instead
command=enter(...) will execute enter(...) immediately and then assign None to command option, should use lambda instead
updating strings inside enter() does not automatically update the label and the button, should use .set() on the StirngVar instead
Below is modified code:
def enter(inputtedinfo, randvar, EnterMessage):
# used .set() to update StringVar
randvar.set(inputtedinfo.get())
EnterMessage.set("Submitted!")
def main():
something = tkinter.Tk()
something.title("My First Tkinter Window")
something.geometry("600x400")
randvar = tkinter.StringVar() # changed to StringVar()
EnterMessage = tkinter.StringVar(value="Enter") # changed to StringVar()
inputtedinfo = tkinter.StringVar()
userLabel = tkinter.Label(something, textvariable=randvar) # used textvariable instead of text option
userEntry = tkinter.Entry(something, textvariable=inputtedinfo)
userButton = tkinter.Button(something, textvariable=EnterMessage, command=lambda: enter(inputtedinfo, randvar, EnterMessage))
userEntry.grid(row=0,column=0)
userLabel.grid(row=0,column=1)
userButton.grid(row=0,column=2)
something.mainloop()
I have a function that opens a top-level window, places a listbox widget, populates it, and then on close should store those selected values to a list, but I am getting an error that I can't seem to find in any google search. Any ideas?
def select_exer():
#Create the new Window
sel_exer_window = tk.Toplevel(main)
sel_exer_window.geometry("+%d+%d" % (x+1150,y+180))
#Descriptive Text
exer_text = tk.Label(sel_exer_window, text="Select your exercises").pack()
exercise_list = tk.Listbox(sel_exer_window, selectmode='multiple')
exercise_list.pack()
def poplist():
lst = exer_options
exercise_list.insert("end",*lst)
poplist()
def on_closing():
#Unlocks the next Button
COUNT_DOWN_BUTTON.config(state="normal")
#Closes the Window
sel_exer_window.destroy()
#lst_var = [exercise_list.get(idx) for idx in exercise_list.curselection()]
lst_var = [exercise_list.get(idx) for idx in exercise_list.curselection()]
print(type(lst_var))
sel_exer_window.protocol("WM_DELETE_WINDOW", on_closing)
You are calling sel_exer_window.destroy() which calls exercise_list.destroy() which destroys the Listbox. After that you are trying to get its values using lst_var = [exercise_list.get(idx) for idx in exercise_list.curselection()]. You can't get anything out of a destroyed widget. I suggest you change that to:
def on_closing():
#Unlocks the next Button
COUNT_DOWN_BUTTON.config(state="normal")
#lst_var = [exercise_list.get(idx) for idx in exercise_list.curselection()]
lst_var = [exercise_list.get(idx) for idx in exercise_list.curselection()]
print(type(lst_var))
#Closes the Window
sel_exer_window.destroy()
I am creating a flashcard app to help with my language studies. The basic idea of the app is to loop through a list of words randomly while I give the answers. As I answer the flash cards the program brings up information for the word on the card, translation, example sentences, etc. Each time a new card comes a 'question_frame' is created and an 'answer_frame' is created for the card, the loop continue until all cards have been answered correctly. I have a feeling that this loop is somehow interfering with the bind as when I try to bind a button to the main window I get an error response in my command window, while nothing happens in the command window if I try to bind to the question or answer frame.
I've tried many of the answers to this kind of problem I have found on this website, binding to a function, using a lambda expression, etc. But nothing has been successful.
import tkinter as tk
import random
def get_random_deck():
new_deck = to_study
r = random.SystemRandom()
r.shuffle(to_study)
return new_deck
def question_page(deck):
question_frame = tk.Frame(window, width = 600, height = 600)
question_frame.grid(row = 0, column = 0, sticky = 'news')
question_label = tk.Label(question_frame, text = deck[0][0])
question_label.grid(row = 1, column = 1, columnspan = 2)
question_entry = tk.Entry(question_frame)
question_entry.grid(row = 2, column = 1, columnspan = 2)
var = tk.BooleanVar(False)
check_button = tk.Button(question_frame, text = 'Check Answer', command = set_value(var))
check_button.grid(row = 3, column = 1, columnspan = 2)
question_frame.bind('<Return>', (lambda event: check_button.invoke()))
check_button.wait_variable(var)
answer = question_entry.get()
return answer
def answer_page(deck,color):
answer_frame = tk.Frame(window, width = 600, height = 600)
answer_frame.grid(row = 0, column = 0, sticky = 'news')
var = tk.BooleanVar(False)
next_button = tk.Button(answer_frame, text = 'Next', command = set_value(var))
next_button.grid(row = 3, column = 1, sticky = 'n')
answer_frame.bind('<Return>', (lambda event: next_button.invoke()))
next_button.wait_variable(var)
def set_value(var):
value = lambda: var.set(True)
return value
def study_loop():
deck = get_random_deck()
while len(deck) > 0:
r = random.SystemRandom()
r.shuffle(deck)
answer = question_page(deck)
if answer in deck[0][1]:
answer_page(deck, 'chartreuse2')
to_study = [['Big', 'small'], ['small', 'big']]
window = tk.Tk()
window.geometry('600x600')
study_loop()
window.mainloop()
I am using a BooleanVar to help move the loop along, perhaps there is a better way to do this but with my limited knowledge and experience this is the only way I could make it work.
So In summary I would like to be able to bind the enter key to input the flashcard answer on the question frame and then to advance from the answer page.
So the solution turned out to be that I need to set the focus to the correct widget as I go through the study_loop.
I set focus on the Entry widget on the question frame and then bound the button invoke to that widget also.
question_entry.focus_set()
question_entry.bind('<Return>', lambda event: check_button.invoke())
and for the answer frame I set the focus and the key bind to the frame itself.
answer_frame.focus_set()
answer_frame.bind('<Return>', lambda event: check_button.invoke())
Now when I hit the enter key on each frame the loop continues on to the next frame.
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)
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.