The problem here is that I cannot disable the button before closing it, how can I do that?
import tkinter as tk
w_window = tk.Tk()
w_window.title('Introduction')
w_window.geometry("450x100")
def tez():
w_button['state'] = 'disabled'
w_button = tk.Button(w_window,
text='Agree',
font = 'Bold 11',
command=tez)
w_button.pack()
if (w_button['state'] == 'disabled'):
w_window.destroy()
w_window.mainloop()
I expected it to disable the button before closing it, but what happened is that it closed without disabling the button.
Your code has indentation errors at the time I am writing my answer, please fix them.
Coming to the point, every Tkinter widget has an after() method, with the following syntax:
<widget-name>.after(time, callback=None)
The time argument takes a value as an int in milliseconds, and callback argument takes a function to repeat after every time interval.
For example if root is the main window,
root.after(100, my_function)
This will call my_function function every 100 milliseconds or 1 second. Notice that unlike sleep() method, this does not halt the main thread, and executes as an independent thread, so the GUI will not pause.
So what you can do is check for the state of w_button after every 50 milliseconds (you can change it as per your wish), and when it gets disabled, the window will destroy().
Here's your full code with the solution implemented:
import tkinter as tk
w_window = tk.Tk()
w_window.title('Introduction')
w_window.geometry("450x100")
def tez():
w_button['state'] = 'disabled' # fixed indentation
w_button = tk.Button(w_window, text='Agree', font = 'Bold 11', command=tez)
w_button.pack()
def check_state():
if (w_button['state'] == 'disabled'): # fixed indentation
w_window.destroy() # fixed indentation
w_window.after(50, check_state) # after every 50 milliseconds, check_state() method is called
check_state()
w_window.mainloop()
Now your code will run as expected. Note that you can make the destroy() method to implement faster by modifying the time in w_window.after() method to a lesser value.
Hope my answer helps. :)
Refer to this article for further research: after Method - tutorialspoint
Related
I am making a game that requires from a user to type a proper phylum, based on a given photo of a species. I provide a GUI for my game. Currently, I am struggling to limit the time that user have to give his answer. I tried with Timer (threading), but idk how to cancel a thread when user doesn't exceed a maximum time.
Here is my code for the button that is used to confirm answer:
time_answer = 10
def confirm_action():
global img_label, answer, n
from_answer = answer.get().lower()
if n == len(files) - 1:
answer_check(from_answer, round_up(end - start, 2))
confirm.configure(text="Done")
answer.configure(state=DISABLED)
n += 1
elif n == len(files):
root.quit()
else:
answer_check(from_answer, "(Press shift)")
answer.bind("<Shift_L>", insert_text)
n += 1
img_f = ImageTk.PhotoImage(Image.open(f"program_ib_zdjecia/faces/{directories[n]}/{files[n]}"))
img_label.configure(image=img_f)
img_label.image = img_f
t = Timer(time_answer, confirm_action)
t.start()
confirm = Button(root, text="Confirm", command=confirm_action)
As #Bryan Oakley said, you can use tkinter's after() method to set a timer that disables user input, but only if the user doesn't submit input within the certain amount of time.
I'll have the explanations here, and a simple example will be at the bottom.
Setting a timer using after()
First, how to set a timer using after(). It takes two arguments:
The number of milliseconds to wait before calling the function, and
The function to call when it's time.
For example, if you wanted to change a label's text after 1 second, you could do something like this:
root.after(1000, lambda: label.config(text="Done!")
Canceling the timer using after_cancel()
Now, if the user does submit the input within the given amount of time, you'll want some way of cancelling the timer. That's what after_cancel() is for. It takes one argument: the string id of the timer to cancel.
To get the id of a timer, you need to assign the return value of after() to a variable. Like this:
timer = root.after(1000, some_function)
root.after_cancel(timer)
Example
Here's a simple example of a cancel-able timer using a button. The user has 3 seconds to press the button before it becomes disabled, and if they press the button before time is up, the timer gets canceled so that the button never gets disabled.
import tkinter
# Create the window and the button
root = tkinter.Tk()
button = tkinter.Button(root, text="Press me before time runs out!")
button.pack()
# The function that disables the button
def disable_button():
button.config(state="disabled", text="Too late!")
# The timer that disables the button after 3 seconds
timer = root.after(3000, disable_button)
# The function that cancels the timer
def cancel_timer():
root.after_cancel(timer)
# Set the button's command so that it cancels the timer when it's clicked
button.config(command=cancel_timer)
root.mainloop()
If you have any more questions, let me know!
I am trying to implement a text widget in tkinter which will allow input text for only a specified time (here 5 secs) and then capture the typed text, without using a submit button calling a function.
I want the time to start as soon as user starts typing and shall prevent user inputting any longer after 5secs. The text that was inputted thus far shall be catured.
I tried the below code which is not working. I tried looking in the documentation and did web search and many stackoverflow discussion threads. I couldn't find an answer. Appreciate inputs on a solution.
from tkinter import *
my_window = Tk()
type_txt = Text()
type_txt.grid(row=0, column=0)
type_txt.focus()
type_txt.after(5000, type_txt.configure(state=DISABLED))
typed_text = type_txt.get("1.0", END)
print(typed_text)
my_window.mainloop()
You can bind <key> event to a function, then inside the callback to disable the text box 5 seconds later using .after().
from tkinter import *
my_window = Tk()
type_txt = Text()
type_txt.grid(row=0, column=0)
type_txt.focus()
def disable_textbox():
type_txt.configure(state=DISABLED)
typed_text = type_txt.get("1.0", END)
print(typed_text)
def start_typing(event):
# disable <Key> binding
type_txt.unbind('<Key>')
# disable text box 5 seconds later
type_txt.after(5000, disable_textbox)
type_txt.bind('<Key>', start_typing)
my_window.mainloop()
so im making a gui miner for a coin called duinocoin, very new to tkinter.
So i integrated mining into a function, so when you press a button it mines. The problem is when I click mine I cant click anyother buttons because its in that mining loop. How do I fix this?
-Eth guy
Using threading, to create a separate thread from the UI thread:
from tkinter import *
from threading import Thread
from time import sleep
root = Tk()
def run(): #the loop function
b1.config(command=Thread(target=run).start) #or b1.config(state=DISABLED)
while True:
print('Hey')
sleep(2) #pause for 2 seconds.
def step(): #the in between function
print('This is being printed in between the other loop')
b1 = Button(root,text='Loop',command=Thread(target=run).start)
b1.pack()
b2 = Button(root,text='Separate function',command=step)
b2.pack()
root.mainloop()
Take a look at this example, first run the loop button, then press the next button and you will notice, it gets executed in between the while loop. But sleep() might still freeze your GUI out.
Explanation:
Threading is nothing much, just like a normal thread, imagine cars going through a thread and they got into accident, so the thread might break, just like that, your tkinter runs on one thread and your while loop runs with it causing the thread to freeze, but with threading you make a new thread for that function with while and hence the thread with tkinter is going smooth, while the thread with while loop is frozen, and it doesnt matter for the other thread.
Alternatively you could also use after() for this purpose, like:
from tkinter import *
from threading import Thread
from time import sleep
root = Tk()
def run():
global rep
print('Hey')
rep = root.after(2000,run) #run the same function every 2 seconds
def stop():
root.after_cancel(rep)
def step():
print('This is being printed in between the other loop')
b1 = Button(root,text='Loop',command=run)
b1.pack()
b2 = Button(root,text='Seperate function',command=step)
b2.pack()
b3 = Button(root,text='Stop loop',command=stop)
b3.pack()
root.mainloop()
Here when you press the loop button, it will start to loop, when you press the separate button, it prints a function in between the pseudo loop and when you press stop, it stops the loop.
after() method takes two arguments mainly:
ms - time to be run the function
func - the function to run after the given ms is finished.
after_cancel() takes the variable name of after() only.
Hopefully, you understood better, do let me know if you have any doubts or errors.
Cheers
I have the following python36 code wich is checking values, and then set a tkinter label that the entry values has been accepted, to then execute another threaded function. Before and after the function as well as inside the function I would like to display something in a tkinter scrolledtext box. Like it where a console/shell.
All the label.configure() as well as scrolledtext.insert() are only getting displayed all at the same time after everything has been run.
I am not able to use the scrolledtext.insert() inside the threaded function (fundamentals question, could I use it inside a function of an imported module?)
I would like to have the execution time of these functions like if I would use the print() function. So execute it as soon as the script went over it.
It would be nice if you could explain to me why this is not executed immideately since I am currently learning python or point me to the appropriate reference.
elif str(x2) == 'None' and str(x3) == 'None':
E2T = 'accepted'
E2L.configure(text=E2T, fg="green")
E2L.grid(row=5, column=2)
E3T = 'accepted'
E3L.configure(text=E3T, fg="green")
E3L.grid(row=6, column=2)
# Start scanning process
scrolledtext.insert(tkinter.INSERT, 'Start scanning....\n' )
print('testprint')
portlist = scan(E1.get(),E2.get(),E3.get())
# try work with returned value and display as in a console
print(portlist)
print('testprint')
scrolledtext.insert(tkinter.INSERT, 'Following Ports are open\n' )
scrolledtext.insert(tkinter.INSERT, str(portlist))
You can do scrolledtext.update_idletasks() after a scrolledtext.insert(), it will refresh the widget and your text will pop.
see that comment for more.
Hope it help!
I want to initialize a window as maximized, but I can't find out how to do it. I'm using python 3.3 and Tkinter 8.6 on windows 7. I guess the answer is just here: http://www.tcl.tk/man/tcl/TkCmd/wm.htm#m8
but I have no idea how to input it into my python script
Besides, I need to get the width and height of the window (both as maximised and if the user re-scale it afterwards), but I guess I can just find that out myself.
You can do it by calling
root.state('zoomed')
If you want to set the fullscreen attribute to True, it is as easy as:
root = Tk()
root.attributes('-fullscreen', True)
However, it doesn't show the title bar. If you want to keep it visible, you can resize the Tk element with the geometry() method:
root = Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
With winfo_width() and winfo_height() you can get the width and height or the window, and also you can bind an event handler to the <Configure> event:
def resize(event):
print("New size is: {}x{}".format(event.width, event.height))
root.bind("<Configure>", resize)
To show maximized window with title bar use the 'zoomed' attribute
root = Tk()
root.attributes('-zoomed', True)
I've found this on other website:
import Tkinter
MyRoot = Tkinter.Tk()
MyRoot.state("zoomed")
MyRoot.mainloop()
This solved my problem.
The first approach is to use the root.state('zoomed'), but is not supposed to be universally available. It works on Windows, and on my Ubuntu machine. However, under my Arch machine it doesn't.
The second is to first get the maxsize, and then set geometry manually, like:
m = root.maxsize()
root.geometry('{}x{}+0+0'.format(*m))
This works on most machines, but not on all. For example, under my Arch the maxsize() returns (1425, 870), while the real geometry of maximized window should be (1440, 848). So, you also couldn't rely on it.
And the third, in my opinion the best approach is to use root.wm_attributes('-zoomed', 1). It is universally available and seems to be the safest. On some machines in could zoom only by width or by height, but comparing to previous method, this one would never give you a window partly ouside of the screen.
Finally, if you want a fullscreen, not just zoomed window, use root.wm_attributes('-fullscreen', 1). It provides a native link to window manager's behavior, thus working much better, than playing with overrideredirect and setting geometry by hand (which on some platforms could lead to unmanaged window, which could be closed only by its own interface or killing the process, won't show on the taskbar, etc...)
The most pythonic is" root.wm_state('zoomed'), as mentioned by #J.F.Sebastian
I recently ran into a similar issue where a library I was supporting needed to add Windows 10 as a development target also. Thanks to the information I found here, This is what we're doing now:
class INI_Link:
"""A connector class between a value stored in an ini file, and a value stored elsewhere that can be get and set with two helper functions."""
def __init__(self, getter, setter, varname, inigroup="Settings", inifile=''):
"""Remember that getter is called first to provide the default value.
Then the ini value is read if available, if not the default value is used."""
self._get = getter
self._set = setter
self._save = lambda value :inidb(inifile)[inigroup].__setitem__(varname, getter())
self._load = lambda :inidb(inifile)[inigroup].get(varname, getter())
#first load
self._lastvalue = self._load()
print(self._lastvalue)
self._set(self._lastvalue)
self._callbacks=[]
def trace(self, callback, mode='w'):
"""this only traces for .value.set() not for changes to the underlying value in either location.
if you never touch this again until .commit() at the end of your program, then it will never trigger until then.
call .probe() to force to check for changes without returning anything."""
self.callbacks.append(callback)
def probe(self):
"""check for changes, if there have been any, allert all traces."""
self._monitor(self._get())
def __get__(self):
value = self._get()
self._monitor(value)
return value
def __set__(self, value):
self._set(value)
self._save(value)
self._monitor(value)
def _monitor(value):
"helper to dispatch callbacks"
if value != self._lastvalue:
self._lastvalue = value
for cb in self._callbacks:
try:
cb()
except:
pass
def commit(self):
"""Call this right before getter is no longer useful."""
self._save(self._get())
And then in the main window class's __init__()
self._geometry = INI_Link(self.tkroot.geometry, self.tkroot.geometry, "window_geometry")
try:
#umbuntu and others, not arch
self._zoomed = INI_Link(lambda:self.tkroot.wm_attributes('-zoomed'),
lambda z: self.tkroot.wm_attributes('-zoomed', z)
, "window_zoomed")
except:
#windows and others, not umbuntu
self._zoomed = INI_Link(lambda: self.tkroot.state() == 'zoomed',
lambda z: self.tkroot.state(['normal','zoomed'][z])
, "window_zoomed")
and then when the window is being closed:
#save zoomed state.
self._zoomed.commit()
try:
if self.tkroot.wm_attributes('-zoomed'):
self.tkroot.wm_attributes('-zoomed', False)
self.tkroot.update()
except:
if self.tkroot.state() != 'normal':
self.tkroot.state('normal')
self.tkroot.update()
#save window size in normal state
self._geometry.commit()
With TkAgg as backend this is the only combination that maximized the window without fullscreen:
win_manager = plt.get_current_fig_manager()
win_manager.window.state('zoomed')
win_manager.full_screen_toggle()