I'm using python GUI (TK). I have a button that runs a long procedure - but I want it to be disabled immediately after clicking it.
It looks like this:
button = tk.Button(self, text="blabla", command= lambda: self.foo(param, button)
def foo(self, button):
button.configure (state = "disabled")
#now call the function that takes time
goo()
def goo():
doLongAction()
The problem is the button is disabled only after goo() returns and then foo returns.
Is there a way to disable it right away?
Thanks
You need to update the button widget after you disable it by calling the update_idletasks method:
button = tk.Button(self, text="blabla", command= lambda: self.foo(param, button)
def foo(self, button):
button.configure (state = "disabled")
##########################
button.update_idletasks()
##########################
#now call the function that takes time
goo()
def goo():
doLongAction()
Related
I used tkinter to create a button and assigned a function to it using command parameter. But this function contains some code takes time to execute. I have simulated it here using time.sleep(). I want to remove this button when this button is clicked. For this I called the global variable for button inside the function and then used pack_forget().
from tkinter import *
import time
def signIn():
global login_button
login_button.pack_forget()
# code after this takes some time to run.
time.sleep(10)
login_screen = Tk()
login_button = Button(login_screen, text="Login", width=10, height=1, command=signIn)
login_button.pack()
login_screen.mainloop()
But the problem is that button gets removed only after the function execution is complete (i.e after 10 seconds). Is there any way I can get the button removed as soon as the pack_forget() line gets executed and not wait for the full function to complete execution?
Call update_idletasks method of the login_screen window after removing the button.
From effbot:
update_idletasks()
Calls all pending idle tasks, without processing any other events. This can be used to carry out geometry management and redraw widgets if necessary, without calling any callbacks.
def signIn():
global login_button, login_screen
login_button.pack_forget()
login_screen.update_idletasks()
# code after this takes some time to run.
time.sleep(10)
This worked for me in that situation.
def launch_button(*args):
# button will show new text when this function is done. The second argument
# is the new text we're sending to the button
data_button_widget.configure(text=args[1])
# schedule launch of the next function, 1 ms after returning to mainloop -
# The first argument is the function to launch
data_button_widget.after(1, args[0])
def get_data(*args):
# button will reconfigure to original text when this function is done
data_button_widget.configure(text='Original button text')
<code that takes some time here>
# make a window
wndw = Tk()
# add a frame widget to the window
fram = ttk.Frame(wndw, padding=SM_PAD)
fram.grid(column=0, row=0)
wndw.columnconfigure(0, weight=1)
wndw.rowconfigure(0, weight=1)
# make a button widget
data_button_widget = ttk.Button(fram, command=lambda: launch_button(get_data, 'wait...'))
data_button_widget.grid(column=2, row=4, padx=SM_PAD, pady=LG_PAD)
data_button_widget.configure(text='Original button text')
# start mainloop
wndw.mainloop()
Using update() works:
def signIn():
global login_button, login_screen
login_button.pack_forget()
login_screen.update()
# code after this takes some time to run.
time.sleep(10)
I have some code here:
def __GameOver__(self):
self.canvas.unbind('<Up>');
#other function ---
time.sleep(2)
self.canvas.bind('<Up>', func);
self.root.after(40,self.GameMainLoop)
pass
What I want is that when the game is over , the player can't do anything until the 'other functions' and time.sleep(2) run over. However , while I'm testing , I find that if I keep pressing the button after the canvas.unbind('< Up >'); but before canvas.bind('< Up >');.The message still can be received after canvas.bind('< Up >');.This is not what I want. But I can not find other ways. Is there some wrong about the understanding about the function unbind? Or is there something like 'event sequence' that I should clear before the bind function?
The problem you have is not with the unbinding, it's with sleep. When you call sleep, no events can be processed, so the processing of the events is done after the sleep is over, and when your <Up> button is bound again. Consider unbinding, then using the after method to do the sleeping, and then call a function that binds the key again.
This is an example. It changes the label when you press up and sleeps when you press the button. During this sleep the up key does nothing.
import Tkinter as tk
import random
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(self.root, text='Bla')
self.label.pack()
self.button = tk.Button(self.root, text='Button', command=self.sleep)
self.button.pack()
self.root.bind('<Up>', self.func)
self.root.mainloop()
def func(self, event):
self.label.config(text='Up '+str(random.random()))
def sleep(self):
self.root.unbind('<Up>')
self.label.config(text='sleeping')
self.root.after(2000, self.done_sleeping)
def done_sleeping(self):
self.label.config(text='done sleeping')
self.root.bind('<Up>', self.func)
App()
I have a GUI that has Entry widget and a submit Button.
I am basically trying to use get() and print the values that are inside the Entry widget. I wanted to do this by clicking the submit Button or by pressing enter or return on keyboard.
I tried to bind the "<Return>" event with the same function that is called when I press the submit Button:
self.bind("<Return>", self.enterSubmit)
But I got an error:
needs 2 arguments
But self.enterSubmit function only accepts one, since for the command option of the Button is required just one.
To solve this, I tried to create 2 functions with identical functionalities, they just have different number of arguments.
Is there a more efficient way of solving this?
You can create a function that takes any number of arguments like this:
def clickOrEnterSubmit(self, *args):
#code goes here
This is called an arbitrary argument list. The caller is free to pass in as many arguments as they wish, and they will all be packed into the args tuple. The Enter binding may pass in its 1 event object, and the click command may pass in no arguments.
Here is a minimal Tkinter example:
from tkinter import *
def on_click(*args):
print("frob called with {} arguments".format(len(args)))
root = Tk()
root.bind("<Return>", on_click)
b = Button(root, text="Click Me", command=on_click)
b.pack()
root.mainloop()
Result, after pressing Enter and clicking the button:
frob called with 1 arguments
frob called with 0 arguments
If you're unwilling to change the signature of the callback function, you can wrap the function you want to bind in a lambda expression, and discard the unused variable:
from tkinter import *
def on_click():
print("on_click was called!")
root = Tk()
# The callback will pass in the Event variable,
# but we won't send it to `on_click`
root.bind("<Return>", lambda event: on_click())
b = Button(root, text="Click Me", command=frob)
b.pack()
root.mainloop()
You could also assign a default value (for example None) for the parameter event. For example:
import tkinter as tk
def on_click(event=None):
if event is None:
print("You clicked the button")
else:
print("You pressed enter")
root = tk.Tk()
root.bind("<Return>", on_click)
b = tk.Button(root, text='Click Me!', command=on_click)
b.pack()
root.mainloop()
I am tring to unittest my tkitner GUI.
Therefore I tried to generate click events from a separate thread.
Here is an example testing the Tkinter.Button:
import unittest, threading
from Tkinter import *
class clickThread(threading.Thread):
def __init__(self, root):
threading.Thread.__init__(self)
self.root = root
def run(self):
button = filter(lambda a: isinstance(a, Button), self.root.children.values())[0]
print button
button.focus()
button.event_generate("<Button-1>")
button.event_generate("<ButtonRelease-1>")
print "clicked"
class Test(unittest.TestCase):
def testName(self):
root = Tk()
button = Button(root, command=self.returnEvent)
button.pack()
thread = clickThread(root)
thread.start()
root.mainloop()
def returnEvent(self):
print "!"
The method Test.returnEvent is not called by my generated click event. But it works as expected if I do a real click.
If I recall correctly (and I may not since its been years since I tried this) the cursor needs to be over tne button for the binding to fire.
Are you aware of the "invoke" method of buttons? You can use it to simulate the pressing of the buttun.
Edit:
let me include my code so I can get some specific help.
import Tkinter
def goPush():
win2=Tkinter.Toplevel()
win2.geometry('400x50')
Tkinter.Label(win2,text="If you have prepared as Help describes select Go otherwise select Go Back").pack()
Tkinter.Button(win2,text="Go",command=bounceProg).pack(side=Tkinter.RIGHT,padx=5)
Tkinter.Button(win2, text="Go Back", command=win2.destroy).pack(side=Tkinter.RIGHT)
def bounceProg():
d=1
print d
root=Tkinter.Tk()
root.geometry('500x100')
Tkinter.Button(text='Go', command=goPush).pack(side=Tkinter.RIGHT,ipadx=50)
root.mainloop()
So when you run the program it opens a window that says Go. Then Go opens a window that asks if youve read the help(which I didnt include in this code sample) and offers Go Back(which goes back) and Go. When you select Go it calls a function which prints 1. After it prints 1 I want the Window to close returning to the original window containing the Go button. How do I do such a thing?
#Kosig It won't quit root. Ie. self.foo = tk.Toplevel(self) and then self.foo.destroy()
For example:
class Foo(tk.Frame):
"""Foo example"""
def __init__(self, master=None):
"""Draw Foo GUI"""
tk.Frame.__init__(self, master)
self.grid()
self.draw_window_bar()
def draw_window_bar(self):
"""Draw bar TopLevel window"""
self.window_bar = tk.Toplevel(self)
# Some uber-pythonian code here...
ask_yes_or_no = messagebox.askyesno('Brian?', 'Romani Ite Domum')
if not ask_yes_or_no:
self.window_bar.destroy()
You have one main object, which is Foo. Foo has one main window (called "frame"), which it gets from tk.Frame. Afterwards, all Toplevel windows (frames) must be created within it. So, your new window here is self.window_bar and all its "objects" are in there, including the method for destroying it (self.window_bar.destroy()). You can call self.window_bar.destroy() from any part of the code, but here it is called after the user clicks "no".
If you create a toplevel window with the Toplevel command, you destroy it with the destroy method of the window object. For example:
import Tkinter as tk
class MyToplevel(tk.Toplevel):
def __init__(self, title="hello, world", command=None):
tk.Toplevel.__init__(self)
self.wm_title(title)
button = tk.Button(self, text="OK", command=lambda toplevel=self: command(toplevel))
button.pack()
if __name__ == "__main__":
def go(top):
print "my work here is done"
top.destroy()
app = tk.Tk()
t = MyToplevel(command=go)
t.wm_deiconify()
app.mainloop()
Apparently you just call quit on the root object that's running your mainloop
edit: All Tkinter widgets have a destroy() method which destroys that widget and its children. So you should be able to call this on your Toplevel