Tkinter only lets you use 1 button at a time? - python-3.x

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

Related

How to stop/pause a worker thread, do an operation in the main thread and send the results to the worker thread in python?

I have a script that uses user input and based on that it generates some data (code below). The script uses threads so the GUI does not freeze while the background tasks are running.
My question is if it is possible to pause the worker thread, ask the user for some input, send it to the worker thread and then to continue the operation?
from tkinter.constants import LEFT, RIGHT, S
import tkinter.messagebox
import tkinter as tk, time, threading, random, queue
from tkinter import NORMAL, DISABLED, filedialog
class GuiPart(object):
def __init__(self, master, client_instance):
canvas = tk.Canvas(master, height=600, width=1020, bg="#263D42")
canvas.pack()
self.button1 = tk.Button(master, text="Mueller Matrix - MMP mode", padx=10,
pady=5, bd = 10, font=12, command=client_instance.start_thread1)
self.button1.pack(side = LEFT)
def files(self):
self.filena = filedialog.askopenfilenames(initialdir="/", title="Select File",
filetypes = (("comma separated file","*.csv"), ("all files", "*.*")))
return self.filena
def processing_done(self):
tkinter.messagebox.showinfo('Processing Done', 'Process Successfully Completed!')
return
class ThreadedClient(object):
def __init__(self, master):
self.running=True
self.gui = GuiPart(master, self)
def start_thread1(self):
thread1=threading.Thread(target=self.worker_thread1)
thread1.start()
def worker_thread1(self):
if self.running:
self.gui.button1.config(state=DISABLED) # can I disable the button from the main thread?
user_input = self.gui.files() # can I pause the Worker thread here so ask the user_input from main loop?
time.sleep(5) # to simulate a long-running process
self.gui.processing_done()
self.gui.button1.config(state=NORMAL)
root = tk.Tk()
root.title('test_GUI')
client = ThreadedClient(root)
root.mainloop()
I am asking this as from time to time my script ends abruptly saying that the GUI is not part of the main loop. Thank you for your help!
In general, you should run the GUI from the main thread.
There is some confusion whether tkinter is thread-safe. That is, if one can call tkinter functions and methods from any but the main thread. The documentation for Python 3 says “yes”. Comments in the C source code for tkinter say “its complicated” depending on how tcl is built.
You should be able to call tkinter functions and methods from additional threads in Python 3 if your tkinter was built with thread support. AFAIK, the tkinter that comes with the downloads from python.org have threading enabled.
If you want to be able to pause additional threads, you should create a global variable, e.g. pause = False.
The extra threads should regularly read the value of this and pause their calculations when set to true, e.g.:
def thread():
while run:
while pause:
time.sleep(0.2)
# do stuff
Meanwhile the GUI running in the main thread should be the only one to write the value of pause, e.g. in a widget callback.
Some sample callbacks:
def start_cb():
pause = False # So it doesn't pause straight away.
run = True
def stop_cb():
pause = False
run = False
def pause_cb():
pause = True
def continue_cb():
pause = False
If you have more complicated global data structures that can be modified be several threads, you should definitely guard access to those with a Lock.
Edit1
Threaded tkinter programs without classes:
Simple threaded tkinter example program
Real-world example for unlocking ms-excel files
Edit2:
From the python source code for _tkinter:
If Tcl is threaded, this approach won't work anymore. The Tcl
interpreter is only valid in the thread that created it, and all Tk
activity must happen in this thread, also. That means that the
mainloop must be invoked in the thread that created the
interpreter. Invoking commands from other threads is possible;
_tkinter will queue an event for the interpreter thread, which will
then execute the command and pass back the result. If the main
thread is not in the mainloop, and invoking commands causes an
exception; if the main loop is running but not processing events,
the command invocation will block.
You can read the whole comment here.

How to make a time limit for answer in python game with GUI?

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!

How to close window after user agreed with terms and conditions

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

Pausing a Thread on PyGTK

I have a button that when it is clicked, it starts a GObject thread that calls a function every 2 seconds. I want to have another button that pauses or stops the calling of this function. Can anyone lead me in the right direction?
Using Python GTK 3
def on_refreshbutton_clicked(self,widget):
print("Data..")
self.th = GObject.timeout_add(2000,self.refresh_screen)
def on_pausebutton_clicked(self,widget):
print("Paused..")
Found the answer. Revome the source!
def on_refreshbutton_clicked(self,widget):
print("Data..")
self.th = GObject.timeout_add(2000,self.refresh_screen)
def on_pauseButton_clicked(self, widget):
print("Paused main screen.")
GObject.source_remove(self.th)

Multiprocessing - tkinter pipeline communication

I have a question on multiprocessing and tkinter. I am having some problems getting my process to function parallel with the tkinter GUI. I have created a simple example to practice and have been reading up to understand the basics of multiprocessing. However when applying them to tkinter, only one process runs at the time. (Using Multiprocessing module for updating Tkinter GUI) Additionally, when I added the queue to communicate between processes, (How to use multiprocessing queue in Python?), the process won't even start.
Goal:
I would like to have one process that counts down and puts the values in the queue and one to update tkinter after 1 second and show me the values.
All advice is kindly appreciated
Kind regards,
S
EDIT: I want the data to be available when the after method is being called. So the problem is not with the after function, but with the method being called by the after function. It will take 0.5 second to complete the calculation each time. Consequently the GUI is unresponsive for half a second, each second.
EDIT2: Corrections were made to the code based on the feedback but this code is not running yet.
class Countdown():
"""Countdown prior to changing the settings of the flows"""
def __init__(self,q):
self.master = Tk()
self.label = Label(self.master, text="", width=10)
self.label.pack()
self.counting(q)
# Countdown()
def counting(self, q):
try:
self.i = q.get()
except:
self.label.after(1000, self.counting, q)
if int(self.i) <= 0:
print("Go")
self.master.destroy()
else:
self.label.configure(text="%d" % self.i)
print(i)
self.label.after(1000, self.counting, q)
def printX(q):
for i in range(10):
print("test")
q.put(9-i)
time.sleep(1)
return
if __name__ == '__main__':
q = multiprocessing.Queue()
n = multiprocessing.Process(name='Process2', target=printX, args = (q,))
n.start()
GUI = Countdown(q)
GUI.master.mainloop()
Multiprocessing does not function inside of the interactive Ipython notebook.
Multiprocessing working in Python but not in iPython As an alternative you can use spyder.
No code will run after you call mainloop until the window has been destroyed. You need to start your other process before you call mainloop.
You are calling wrong the after function. The second argument must be the name of the function to call, not a call to the function.
If you call it like
self.label.after(1000, self.counting(q))
It will call counting(q) and wait for a return value to assign as a function to call.
To assign a function with arguments the syntax is
self.label.after(1000, self.counting, q)
Also, start your second process before you create the window and call counting.
n = multiprocessing.Process(name='Process2', target=printX, args = (q,))
n.start()
GUI = Countdown(q)
GUI.master.mainloop()
Also you only need to call mainloop once. Either position you have works, but you just need one
Edit: Also you need to put (9-i) in the queue to make it count down.
q.put(9-i)
Inside the printX function

Resources