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.
Related
Before you read:
I am a complete novice at programming let alone Python. I don't expect solutions. I will appreciate it even if I just get a pointer in the right direction. If you feel the way I've requested help is unrefined, please let me know so next time I need help, I'll do a better job at asking.
Goal:
I'm making a program to test how to get a program to respond depending on which widget has to focus on. Currently, I'm just having it respond by displaying 'what' has focus.
Eventually, once I've figured this out I can implement what I've learnt into another program I'm working on was focusing on an entry field will result in the field clearing of all input. So naturally, I will want this program to not only change the label but the entry widget too. That will be for later.
Progress:
I've managed to have the program print which widget has a focus in both the terminal and in a label widget.
Problem:
My issue is that the message is ugly, and I want it to look neater.
Example of the problem:
Instead of saying "Entry has focus", it says ".!frame.!entry {has focus}"
I have tried:
Checking if other's have made similar programs. The only one I found was how I made this much progress but it didn't make the text nicer. I've also done research on various sites on how Python handles certain commands I've used in this program.
I am a novice with programming so I admit I'm not completely sure what the best way to run this program is and so trying to research it is difficult for me.
Code:
# Call tkinter tools
from tkinter import *
from tkinter import ttk
"""
TODO:
Add different widgets that can 'gain' focus.
Print in the terminal which widget has focus.
Change entry and label text depending on which widget has focus.
"""
# Develop the window and frame
root = Tk()
root.title("Focus Test")
root.columnconfigure(0, weight = 1)
root.rowconfigure(0, weight = 1)
frame = ttk.Frame(root, padding = "1 1 1 1")
frame.grid(column = 0, row = 0, sticky = (N, S, E, W))
# Resizes columns in frame
for col in range(1, 3):
frame.columnconfigure(col, weight = 1)
# Resizes rows in frame
for row in range(1, 3):
frame.rowconfigure(row, weight = 1)
# Add response label
foc_labvar = StringVar()
foc_labvar.set("No focus")
foc_lab = ttk.Label(frame, width = 7, textvariable = foc_labvar)
foc_lab.grid(column = 2, row = 2, sticky = (W, E))
# Add entry box
foc_entvar = StringVar()
foc_entvar.set("Entry widget")
foc_ent = ttk.Entry(frame, width = 7, textvariable = foc_entvar)
foc_ent.grid(column = 1, row = 1, sticky = (W, E))
# Add button
foc_butvar = StringVar()
foc_butvar.set("Button widget")
foc_but = ttk.Button(frame, width = 7, textvariable = foc_butvar)
foc_but.grid(column = 2, row = 1, sticky = (W, E))
# Focus commands
def focus(event):
focused_widget = frame.focus_get()
foc_labvar.set((focused_widget, "has focus"))
print(focused_widget, "has focus")
# Bind mouse click to run focus command
root.bind("<Button-1>", focus)
# Resize widgets inside frame
for child in frame.winfo_children():
child.grid_configure(padx = 5, pady = 5)
root.mainloop()
You can just easily do this having event handle the widget identification part, like:
def focus(event):
focused_widget = event.widget.winfo_class()
foc_labvar.set((focused_widget, "has focus")) # Or f'{focused_widget} has focus'
print(focused_widget, "has focus")
Here event will provide the widget with widget method and then you can use any widget methods on it, like focus() or insert()(if its an entry widget) and so on. Here winfo_class should work, because it is a universal method and works with all the widgets.
More info on winfo_class, it will provide the class of the widget, like TButton for buttons, because that is how tk refers to them. If you adamantly still want to get rid of the T then just go for event.widget.winfo_class()[1:] to trim the 'T' off.
You can give widgets a name of your choosing. You can then use winfo_name to get the name of the widget. About the only required for a name is that it cannot have a period in it.
Also, you should bind to <FocusIn> and <FocusOut> so that your code works even if the focus changes via the keyboard.
foc_ent = ttk.Entry(..., name="foc entry")
foc_but = ttk.Button(..., name="foc button")
...
def focus(event):
focused_widget = frame.focus_get()
if focused_widget:
foc_labvar.set(f"{focused_widget.winfo_name()} as focus")
else:
foc_labvar.set("nothing has focus")
root.bind("<FocusIn>", focus)
root.bind("<FocusOut>", focus)
You can just use an if statement like this:
def focus(event):
focused_widget = frame.focus_get() # You can also use `event.widget` like what #CoolCloud did.
if focused_widget == foc_ent:
foc_labvar.set("Entry has focus")
if focused_widget == foc_but:
foc_labvar.set("Button has focus")
Combining the advice provided. Here is what I've ended up with which is working the way I had imagined in my head plus a little extra from everyone's help:
# Focus commands
def focus(event):
focused_widget = event.widget.winfo_class()[1:]
foc_labvar.set(focused_widget + " has focus")
foc_entvar.set(focused_widget + " has focus")
print(focused_widget, "has focus")
# Bind mouse and keyboard to run focus command
root.bind("<Button-1>", focus)
root.bind("<FocusIn>", focus)
root.bind("<FocusOut>", focus)
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
Well, I never really worked in Python before this project. I am doing it because it's an assignment from a teacher (even tough we never had a Python class) and, of course, because learning a new language is always good.
Anyways this code is working as intended until I add another instance of the AND class. When I add it in my code things are going crazy. I would appreciate if you would check how my code works with and without both test instances of the AND class. While we are at it I would really appreciate if one of you guys/girls could tell me how to make the AND button create a new instance of the class (for example in c++ I would use new AND, I want something similar when I press the button).
EDIT: As someone pointed i was not clear enough. What i meant by things go crazy
is that when i add more than 1 instance of the class the click and drag function i'm trying to do is not working properly anymore. The click and drag is not working anymore for one square and for the other one it will just follow my mouse whenever i go around it, even tought i'm not clicking. I really belive that this is because i have this int the init function :
self.workspace.tag_bind(self.patrat,"<Enter>",self.whileInside)
workspace.bind("<ButtonRelease-1>",self.onRelease)
I think it doesn't belong there but i really have no ideea how to desing my code and where to put this, if that's the problem
Here's my code so far:
import tkinter as tk
from tkinter import*
def doNothing():
print("nothing happens bro")
root = Tk()
#========= Drop down menu/toolbar
menu = Menu(root)
root.config(menu=menu)
subMenu = Menu(menu)
menu.add_cascade(label="file", menu=subMenu)
subMenu.add_command(label="New Project...",command=doNothing)
subMenu.add_command(label="New",command=doNothing)
subMenu.add_separator()
subMenu.add_command(label="exit",command=doNothing)
editMenu = Menu(menu)
menu.add_cascade(label="edit", menu=editMenu)
editMenu.add_command(label="redo", command=doNothing)
#========Canvas
workspace=tk.Canvas(root, width=740, height=540,bg="white")
workspace.grid(row=0,column=1)
#====DRAW FUNCTIONS
# Draw rectangle
class AND():
def __init__(self):
x=10
y=10
self.workspace=workspace
self.patrat = workspace.create_rectangle(x , y , x + 40 , y + 40,fill = "blue")
self.workspace.tag_bind(self.patrat,"<Enter>",self.whileInside)
print("self.workspace.tag_bind(self.patrat,<Enter>,self.whileInside)")
workspace.bind("<ButtonRelease-1>",self.onRelease)
print(" workspace.bind(<ButtonRelease-1>,self.onRelease)")
def whileInside(self,event):
self.workspace.tag_bind(self.patrat,"<Button-1>",self.onClick)
print("self.workspace.tag_bind(self.patrat,<Button-1>,self.onClick)")
def onClick(self,event):
self.workspace.tag_bind(self.patrat,"<Motion>", self.callback)
print("self.workspace.tag_bind(self.patrat,<Motion>, self.callback)")
def onRelease(self,event):
self.workspace.tag_unbind(self.patrat,"<Enter>")
print(" self.workspace.tag_unbind(self.patrat,<Enter>)")
self.workspace.tag_unbind(self.patrat,"<Button-1>")
print("self.workspace.tag_unbind(self.patrat,<Button-1>)")
self.workspace.tag_unbind(self.patrat,"<Motion>")
print(" self.workspace.tag_unbind(self.patrat,<Motion>)")
def callback(self,event):
x, y = event.x, event.y
self.workspace.coords(self.patrat, x, y, x + 40, y + 40)
test=AND()
test2=AND()
#========Left Frame
frame= Frame(root)
frame.grid(row = 0, column = 0, sticky = "n")
leftButton1 = Button(frame,text = "AND",width = 10).grid(row = 1, column = 0)
leftButton2 = Button(frame,text = "OR",width = 10).grid(row = 2, column = 0)
leftButton3 = Button(frame,text = "XOR",width = 10).grid(row = 3, column = 0)
leftButton4 = Button(frame,text = "XNOR",width = 10).grid(row = 4, column = 0)
leftButton5 = Button(frame,text = "NOT",width = 10).grid(row = 5, column = 0)
root.mainloop()
The problem is that the bind command (not tag_bind) does not add a command, it replaces a command. So when your second instance calls workspace.bind("<ButtonRelease-1>",self.onRelease), it removes that from the first instance. To add a command you need the optional 3rd argument, like this: workspace.bind("<ButtonRelease-1>",self.onRelease, '+')
However, there's a much easier way to accomplish what you want. The event also includes information about the mouse buttons. So just bind to motion permanently and check the mouse button before moving:
class AND():
def __init__(self):
x=10
y=10
self.workspace=workspace
self.patrat = workspace.create_rectangle(x , y , x + 40 , y + 40,fill = "blue")
self.workspace.tag_bind(self.patrat,"<Motion>",self.on_motion)
def on_motion(self, event):
if event.state & 256: # if the left mouse button is down
x, y = event.x, event.y
self.workspace.coords(self.patrat, x, y, x + 40, y + 40)
I have a python program in which I've created a few custom modal windows that are children of a top level tkinter window. An example of such a window is below, but I have other, more complicated ones. What I would like to do, but cannot determine how, is to have somewhere that I call this and wait for a response. I've tried something like below, it fails to create the window
modal = ModalWindow(tk.Tk(), 'Title', 'Text')
while modal.choice is None:
pass
if modal.choice == 'Yes':
# Do Something
What is the appropriate way to handle this type of thing?
Custom Modal Window Example
class ModalWindow(object):
def __init__(self, root, title, text):
self.choice = None
# Setup the window
self.modalWindow = tk.Toplevel(root)
self.modalWindow.title(title)
self.modalWindow.resizable(False, False)
# Setup the widgets in the window
label = ttk.Label(self.modalWindow, text = text, font = '-size 10')
label.grid(row = 0, column = 0, columnspan = 2, padx = 2, pady = 2)
but = ttk.Button(self.modalWindow, text = 'Yes', command = self.choiceYes)
but.grid(row = 1, column = 0, sticky = 'nsew', padx = 2, pady = 5)
but = ttk.Button(self.modalWindow, text = 'No', command = self.choiceNo)
but.grid(row = 1, column = 1, sticky = 'nsew', padx = 2, pady = 5)
self.modalWindow.rowconfigure(1, minsize = 40)
def choiceYes(self):
self.choice = 'Yes'
self.modalWindow.destroy()
def choiceNo(self):
self.choice = 'No'
self.modalWindow.destroy()
After some further digging, I found my own answer. The following does what I want. The function wait_window accepts a tkinter window and pauses until that window has been closed.
root = tk.Tk()
modal = ModalWindow(root, 'Title', 'Text')
root.wait_window(modal.modalWindow)
if modal.choice == 'Yes':
# Do Something
I am missing something fundamental here. If the window is application-modal, you're forced to wait: there's nothing programmatic to be done. If you're trying to wait for an event that transpires in a window belonging to ANOTHER application, you're hosed unless you write some thorny OLE code (I assume Windows here, as UNIX way of doing things would never lead one eve to consider such a solution component).
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.