Make a function that wait a parameter to change triggered by button - python

The following is a program that does not make sense, but I want to do something similar in a larger code. One function is called and I want it to wait for a change in a parameter, where the change is triggered by a button. Running this code, when the OK button is pressed it doesn't allow for another button to be pressed and it freezes. Also before anything, I get the error: name 'boton' is assigned to before global declaration. Thanks for reading. Saludos.
from Tkinter import *
import time
master = Tk()
def callback():
w1['text']="this"
while boton==0:
time.sleep(1)
w1['text']="and then this"
def switch():
if boton==0:
global boton
boton=1
if boton==1:
global boton
boton=0
boton=0
w1 = Label(master, text="")
w1.grid(row=0, column=0)
e1 = Entry(master)
e1.grid(row=0, column=1)
b = Button(master, text="OK", command=callback)
b.grid(row=1, column=0)
b2 = Button(master, text="switch", command=switch)
b2.grid(row=1, column=1)
mainloop()

You have a few problems and the two big ones are interrupting the Tkinter mainloop().
When you press OK your program gets stuck in a while loop that can never be broken. Keep in mind Tkinter runs on a single thread and any time you create a loop then it blocks the mainloop() form working until that loop is broken. To get around something like this we can use the after() method to schedule and event to happen as part of the mainloop() instead. The second problem that is blocking the mainloop() is the sleep() method. This will have the same affect until the time is up.
We also need to make sure you are using if/else statements because your Switch() your if statements will always result in the 2nd text.
With that taken care of all we need to do now is a little cleanup.
Instead of doing from Tkinter import * we should do import Tkinter as Tk. This will keep us from overriding any methods from other imports or from variables we create.
You do not need to do global in each if statement. You just need it at the top of your function.
take a look at the below code.
import Tkinter as tk
master = tk.Tk()
def callback():
global boton, w1
w1.config(text="this")
if boton == 0:
master.after(1000, callback)
else:
w1.config(text="and then this")
def switch():
global boton
if boton==0:
boton=1
else:
boton=0
boton=0
w1 = tk.Label(master, text="")
w1.grid(row=0, column=0)
e1 = tk.Entry(master)
e1.grid(row=0, column=1)
tk.Button(master, text="OK", command=callback).grid(row=1, column=0)
tk.Button(master, text="switch", command=switch).grid(row=1, column=1)
master.mainloop()

Related

tkinter bindo to a button or change the command of a bind

Ive made a game where at the end of the game, I change the command that is called by the button.
I was wondering if there was a way to link the bind to the button rather than the command, or if I could change the command called by the bind.
Here is my code for the button:
# create the button
self.but = Button(win, text="Submit", command=lambda: self.check_input(win))
self.but.grid(row=10, column=2, columnspan=3, sticky="NEWS")
win.bind("<enter>", self.check_input(win))
# change the button command
self.but["command"] = lambda: self.next(win, self.game_init)
self.but["text"] = "PLAY AGAIN!"
If your function does not use Event passed by tkinter when a widget is bind-ed, then it is fairly simple:
def check_input(self,win,event=None):
....
....
self.but = Button(win, text="Submit", command=lambda: self.check_input(win))
....
win.bind("<enter>", lambda e: self.check_input(win,e))
Though an easier and dynamic way to always follow the buttons command is(like jasonharper said) but you cannot use e at all, even if triggered by the given event:
win.bind("<enter>", lambda e: self.but.invoke())
An example to account for using same function with bind and command of a button:
from tkinter import *
root = Tk()
def func(win,e=None):
if e is not None:
print(f'Triggered by the hitting the {e.keysym} key')
else:
print('Triggered by the pressing button')
but = Button(root,text='Click me',command=lambda: func(root))
but.pack()
root.bind('<Return>',lambda e: func(root,e))
root.mainloop()
Also a good time to mention, your event is wrong, either you meant '<Return>'(enter key) or '<Enter>'(when you enter the bounds of a widget with cursor)

Button.wait_variable usage in Python/Tkinter

There have already been several topics on Python/Tkinter, but I did not find an answer in them for the issue described below.
The two Python scripts below are reduced to the bare essentials to keep it simple. The first one is a simple Tkinter window with a button, and the script needs to wait till the button is clicked:
from tkinter import *
windowItem1 = Tk()
windowItem1.title("Item1")
WaitState = IntVar()
def submit():
WaitState.set(1)
print("submitted")
button = Button(windowItem1, text="Submit", command=submit)
button.grid(column=0, row=1)
print("waiting...")
button.wait_variable(WaitState)
print("done waiting.")
windowItem1.mainloop()
This works fine, and we see the printout “done waiting” when the button is clicked.
The second script adds one level: we first have a menu window, and when clicking the select button of the first presented item, we have a new window opening with the same as above. However, when clicking the submit button, I don’t get the “Done waiting”. I’m stuck on the wait_variable.
from tkinter import *
windowMenu = Tk()
windowMenu.title("Menu")
def SelectItem1():
windowItem1 = Tk()
windowItem1.title("Item1")
WaitState = IntVar()
def submit():
WaitState.set(1)
print("submitted")
button = Button(windowItem1, text="Submit", command=submit)
button.grid(column=0, row=1)
print("waiting...")
button.wait_variable(WaitState)
print("done waiting")
lblItem1 = Label(windowMenu, text="Item 1 : ")
lblItem1.grid(column=0, row=0)
btnItem1 = Button(windowMenu, text="Select", command=SelectItem1)
btnItem1.grid(column=1, row=0)
windowMenu.mainloop()
Can you explain it?
Inside your SelectItem1 function, you do windowItem1 = Tk(). You shouldn't use Tk() to initialize multiple windows in your application, the way to think about Tk() is that it creates a specialized tkinter.Toplevel window that is considered to be the main window of your entire application. Creating multiple windows using Tk() means multiple main windows, and each one would need its own mainloop() invokation, which is... yikes.
Try this instead:
windowItem1 = Toplevel()

Why does my Tkinter GUI freeze when the user presses a button (only when I use threading)?

Ok, continuation of my previous question. I'm making a GUI in Tkinter where, when you press a button, another GUI is generated, where you put in some text and you press a button there and it generates some text inside a Word document. I need the user to fill out the text inputs before defining my variables, though, so that the variables aren't blank. So I'm using the threading module to do that. But, when I use the threading module, or any other way to make Python wait, the first Tkinter GUI freezes after you press a button.
import threading
import tkinter as tk
ra = threading.Event()
def function2():
import docx
from docx import Document
doc = Document("template.docx")
ra.set()
para_1 = doc.add_paragraph(variable)
para_1.add_run(" foo.")
para_1.add_run(variable2)
para_1.add_run(" beep boop.")
doc.save("example.docx")
def function1():
master = tk.Tk()
e1 = tk.Entry(master)
e1.grid(row=0, column=1)
e2 = tk.Entry(master)
e2.grid(row=1, column=1)
tk.Button(master, text="Generate", width=15, command=function2).grid(row=1)
ra.wait()
global variable
variable = (e1.get())
global variable2
variable2 = (e2.get())
r = tk.Tk()
b1 = tk.Button(r, text='example', width=25, command=function1)
b1.pack(padx=5, pady=15)
r.mainloop()
So I expected that this would run as normal, giving me a document with (not empty) variables and pre-defined strings. Problem is, when you press example, the Tkinter GUI freezes and doesn't give you another GUI like I expect.
Everything is running in a single thread because you haven't explicitly run any of your code in a second thread. Your threading.Event object will never be set, so your program will wait forever at the point where you call ra.wait.
I need the user to fill out the text inputs before defining my variables, though, so that the variables aren't blank.
The way to wait for a dialog to be filled out is to use the tkinter method wait_window which will wait for a window to be destroyed before returning. Your button needs to call a function that gets the value from the dialog and then destroy the dialog, which will cause wait_window to return and allow your code to continue.
Also, you need to use Toplevel, not Tk to create additional windows.
Here is one way to use wait_window, based on the code in your question:
import tkinter as tk
def function2():
print('variable:', variable)
print('variable2:', variable2)
def function1():
def close_dialog():
global variable, variable2
variable = e1.get()
variable2 = e2.get()
master.destroy()
master = tk.Toplevel()
e1 = tk.Entry(master)
e2 = tk.Entry(master)
b = tk.Button(master, text="Generate", width=15, command=close_dialog)
e1.grid(row=0, column=1)
e2.grid(row=1, column=1)
b.grid(row=1)
master.wait_window(master)
function2()
r = tk.Tk()
b1 = tk.Button(r, text='example', width=25, command=function1)
b1.pack(padx=5, pady=15)
r.mainloop()

tkinter: Frame in Toplevel displayed in parent

I have currently two problems with Toplevel instances in Tkinter.
First and most important: I want to display a popup window and place 2 frames in it for better arangement in grid, but it doesn't work as I expect it to work:
import tkinter
root = tkinter.Tk()
tkinter.Button(root, text="ABC").grid(column=0, row=0)
tkinter.Label(root, text="FOO").grid(column=1, row=1)
win = tkinter.Toplevel()
f1 = tkinter.Frame(win).grid(row=0, column=0)
f2 = tkinter.Frame(win).grid(row=1, column=1)
tkinter.Label(f1, text="FRAME 1").grid()
tkinter.Label(f2, text="FRAME 2").grid()
root.mainloop()
I would expect "FRAME 1" and "FRAME 2" to be placed in the Toplevel window, but they are actually placed in root. How do I fix this?
Second, less important: The popup window in the code above is spawning behind the root window, while I would like it to be placed in front of root, how do I achieve this?
You set your frames f1 and f2 to the return-value of the grid() command, which is None, therefore tkinter.Label(f1, text="FRAME 1").grid() does not work as you expect.
Try something like this:
win = tkinter.Toplevel()
f1 = tkinter.Frame(win)
f1.grid(row=0, column=0)
tkinter.Label(f1, text="FRAME 1").grid()
When setting your geometry manager be it grid(), pack() or place() and you need to be able to interact with that widget later you will need to assign the widget to a variable and then apply the geometry manager on a new line using that variable name. This way your variable will not be a value of None but rather the proper widget. This happens because the geometry managers all return None.
Next the reason your labels are on the wrong windows is because when your labels try to connect with f1 and f2 they are not able to find a proper tkinter container due to the values being None so it defaults to the root tkinter window in an attempt to be place on something.
With fixing the None issues you will also fix your label issue.
To address the matter of your top level window not being in front of your root window there are a couple of things you can do. The main reason this is happening is how your code is generating the top level at __init__ rather than later with a button or a timed event.
If you really need your top level window to open at the same time as root you can use after() and a function to do this and it will be placed on top. If you do not need it right when the window opens you may want to assign a command to a button to run a function that builds the top window.
Here is an example with after():
import tkinter as tk
root = tk.Tk()
def create_top():
win = tk.Toplevel(root)
f1 = tk.Frame(win)
f1.grid(row=0, column=0)
f2 = tk.Frame(win)
f2.grid(row=1, column=1)
tk.Label(f1, text="FRAME 1").grid()
tk.Label(f2, text="FRAME 2").grid()
tk.Button(root, text="ABC").grid(column=0, row=0)
tk.Label(root, text="FOO").grid(column=1, row=1)
root.after(10, create_top)
root.mainloop()
Here is an example with a button:
import tkinter as tk
root = tk.Tk()
def create_top():
win = tk.Toplevel(root)
f1 = tk.Frame(win)
f1.grid(row=0, column=0)
f2 = tk.Frame(win)
f2.grid(row=1, column=1)
tk.Label(f1, text="FRAME 1").grid()
tk.Label(f2, text="FRAME 2").grid()
tk.Button(root, text="ABC", command=create_top).grid(column=0, row=0)
tk.Label(root, text="FOO").grid(column=1, row=1)
root.mainloop()

Managing script execution using GUI "buttons" from Tkinter module

there is following script:
import sys, Tkinter
def myScript():
...
...
def runScript():
while 1:
myScript()
i want to manage it using GUI "button" from Tkinter module
if __name__ == '__main__':
win = Frame ()
win.pack ()
Label(win, text='Choose following action', font=("Helvetica", 16), width=70, height=20).pack(side=TOP)
Button(win, text='Start script', width=20, height=3, command=runScript).pack(side=LEFT)
Button(win, text='Stop script', width=20, height=3, command=sys.exit).pack(side=LEFT)
Button(win, text='Quit', width=15, height=2, command=win.quit).pack(side=RIGHT)
mainloop()
when i type "Start script" button my script successfully started and working (infinite loop), but then i want to stop execution using "Stop script" button i can not do this, since main window with buttons is unavailable ("not responding")
What must i change in order to use both buttons correctly?
The problem is that the execution of the script is considered blocked, so while it continually runs the control is never returned back to the GUI to be able to continue with any external commands to stop it. To adjust this you will need to use threading. The best way to do this would to subclass your script method with threading.Thread and overloading the .run() method with your script execution. Doing so would look like this:
import threading
class MyScript(threading.Thread):
def __init__(self):
super(MyScript, self).__init__()
self.__stop = threading.Event()
def stop(self):
self.__stop.set()
def stopped(self):
return self.__stop.isSet()
def run(self):
while not self.stopped():
# Put your script execution here
print "running"
From there you can setup a global variable or class variable to keep track of if you currently have a thread running (you may want to do this differently if you want a user to run multiple instances of the script) and methods to start and stop it. I'd recommend a class variable with your application being a class itself but that's up to you.
script_thread = None
def startScript():
global script_thread
# If we don't already have a running thread, start a new one
if not script_thread:
script_thread = MyScript()
script_thread.start()
def stopScript():
global script_thread
# If we have one running, stop it
if script_thread:
script_thread.stop()
script_thread = None
From there you can bind those methods to your buttons. I'm not sure how you have your application structure setup (it seems to me you imported everything from Tkinter or sub-classed the Tkinter.Tk() instance). However in order to do what you propose you will need to use threading to prevent a blocking situation.
Use this:
import sys
from Tkinter import *
import tkMessageBox as tkmsg
win = None
def myScript():
pass
def runScript():
global win
while 1:
win.update()
pass
def btnStop_Click():
tkmsg.showinfo("Stopped", "Stopped")
sys.exit
if __name__ == '__main__':
global win
win = Frame ()
win.pack ()
Label(win, text='Choose following action', font=("Helvetica", 16), width=70, height=20).pack(side=TOP)
Button(win, text='Start script', width=20, height=3, command=runScript).pack(side=LEFT)
Button(win, text='Stop script', width=20, height=3, command=btnStop_Click).pack(side=LEFT)
Button(win, text='Quit', width=15, height=2, command=win.quit).pack(side=RIGHT)
mainloop()

Categories