Tkinter GUI stuck till end of the task when pressing a button - python

When I press the "start program" button, it starts a 5 second task and blocks the GUI.
As i understand, I need to use Threading so each button will work independently from the GUI.
I've been stuck for almost a month already, can someone show me how can execute def start_Button(self): function using threading?
from tkinter import *
import time
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.var = IntVar()
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Exit", command=self.client_exit)
startButton = Button(self, text="Start Program", command=self.start_Button)
quitButton.grid(row=0,column=0)
startButton.grid(row=0, column=2)
def client_exit(self):
exit()
def start_Button(self):
print('Program is starting')
for i in range (5):
print(i)
time.sleep(1)
root = Tk()
root.geometry("200x50")
app = Window(root)
root.title("My Program")
root.mainloop()

There are a lot of important questions to ask before you jump into threading for the first time, but by and large the most important question is "how do I want to communicate between my threads?" In your minimal example you don't require any communication at all, however in your real code start_Button may be doing some Work and returning data back to the GUI. If that's the case, you have more work to do. Please clarify that as a comment if that's the case.
For the minimal example, it's actually quite easy.
class Window(tkinter.Frame):
# the rest of your GUI class as written, but change...
def start_Button(self):
def f():
# this is the actual function to run
print('Program is starting')
for i in range (5):
print(i)
time.sleep(1)
t = threading.Thread(target=f)
t.start()

Related

Python Tkinter commands priority

I have faced a strange problem in my program.
from tkinter import *
import time
class Window:
def __init__(self):
self.root = Tk()
self.root.title('Test')
self.root.geometry('400x500')
self.root.resizable(FALSE, FALSE)
self.root.configure(bg ='#1A181B')
def draw_widgets(self):
Button(self.root, text='Start', font='Verdana 17',command = self.start_bot).grid(row=1, column=1)
def run(self):
self.draw_widgets()
self.root.mainloop()
def start_bot(self):
Button(self.root, text='Start', font='Verdana 17', command=self.start_bot).grid(row=2, column=1)
time.sleep(4)
print('a')
win = Window()
win.run()
win.draw_widgets()
As you can see after pressing a button, I want to create another button, then wait for 4 seconds, then print 'a', but it is doing another thing: 1) Waiting for 4 seconds 2) Printing 'a' 3) Creating button.
Please, how I can fix this, I really need your help.
When you use time.sleep() the application suspends processing until the time period is done. This includes updating the GUI changes. To allow the changes to take effect before sleep is started you have to tell the application to do that with update_idletasks(). See example:
def start_bot(self):
Button(self.root, text='Start', font='Verdana 17',
command=self.start_bot).grid(row=2, column=1)
self.root.update_idletasks() # Update GUI changes
time.sleep(4)
print('a')
Have a look at the after() function, which does not suspend processing but schedules something for a later time. This may often be a good function to use instead of sleep.

Tkinter progress bar during telnet session

I want to push some configlines through a telnetsession from a tkinter gui.
Simplified; in my GUI I have a button. After pressing this button the telnet session starts (it fetches the config lines from an external fileshare).
But at this moment it is not very user friendly, there is now way to tell whether it is still busy or not. I want to fix this by a progress bar popup.
I've got my main script with all the fetching of the configuration files, adapting and sending it to a switch.
I got a "standalone" script with the popup progress bar.
Now I want to combine those two, but it won't work. I've read about multi-threading, but since I'm new to coding I need some help to understand how this works, if it is needed in my case
The mainscript:
#window stuff
window = Tk()
window.geometry('700x500')
window.title("Switchconfig generator")
window.lift()
def btntelnetclicked():
try:
telnet()
except:
messagebox.showinfo("Status", "Something went wrong, config was not pushed.")
#buttons
btntelnet= Button(window, text="Push config", command=btntelnetclicked)
btntelnet.grid(column=2, row=4, padx=10, pady=10)
This is offcourse just a little piece of code
The progressbar
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
else: from tkinter import ttk
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.progbar = ttk.Progressbar(self.root)
self.progbar.config(maximum=4, mode='indeterminate')
self.progbar.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.progbar.start()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
self.root.after(50, self.check_thread)
def check_thread(self):
if self.secondary_thread.is_alive():
self.root.after(50, self.check_thread)
else:
self.progbar.stop()
self.b_start['state'] = 'normal'
def arbitrary():
btntelnetclicked()
gui = GUI_Core()
gui.root.mainloop()

Tkinter newly created button does not execute command

A script should open an application with two buttons visible. When Hello button is pressed a new button is gridded into the row number 1 and Hello button to be deactivated. When this new button is pressed it should delete itself off the grid and reactivate hello button but it does not do it.
Please check the video to see it in action.
Code edited to comment suggestion
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
self.master = master
self.master.geometry('300x100+10+10')
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def new_button(self):
print("enable_b")
self.hi_there.config(state=ACTIVE)
self.new_button.grid_remove()
def say_hi(self):
print("hi there, everyone!")
self.new_button = Button(self)
self.new_button.config(text = "New BTN", command=self.new_button)
self.new_button.grid(row=1,column=0)
self.hi_there.config(state=DISABLED)
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT.config(text="QUIT",fg="red",command=self.quit)
self.QUIT.grid(row=0,column=1)
self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi
self.hi_there.grid(row=0,column=0)
def quit(self):
self.master.destroy()
def testit():
root = Tk()
app = Application(master=root)
app.mainloop()
if __name__ == '__main__':
testit()
Initially, self.new_button refers to a method. Then, you do this:
self.new_button = Button(self)
That effecting removes the method and replaces it with the button widget itself.
Also, you never assign a command to the new button, so clicking it doesn't cause anything to be called.
Where your program will technically work just fine with the 2 correction mentioned in Bryan's answer I am not sure why you are taking all the extra effort configuring the widgets for each individual field. All your configs can be done when you create the widget.
That said you can also change a few things for a cleaner code and 1 change I think that really needs to be made is how you are removing the new_button from the grid. When you do grid_remove() this only takes the widget off the screen but does not get rid of the widget. Then next time you press the say_hi button you will end up creating a new button and the old button will still exist. Instead I think I would use destroy() on the button and then let say_hi recreate it.
See this revised version of your code. You will see what I mean about configuring everything when creating the widget and also you do not need to write your own function for quit you can simply do self.master.destroy in the quit button command.
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.master.geometry('300x100+10+10')
self.create_widgets()
def new_btn(self):
print("enable_b")
self.hi_there.config(state="active")
self.new_button.destroy()
def say_hi(self):
print("hi there, everyone!")
self.new_button = tk.Button(self, text="New BTN", command=self.new_btn)
self.new_button.grid(row=1, column=0)
self.hi_there.config(state="disabled")
def create_widgets(self):
tk.Button(self, text="QUIT", fg="red", command=self.master.destroy).grid(row=0,column=1)
self.hi_there = tk.Button(self, text="Hello", command=self.say_hi)
self.hi_there.grid(row=0, column=0)
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root).pack()
root.mainloop()

Access to Tkinter's Text widget by background thread cause crashes

I created a simple tkinter app, where have used two threads. Their task is to write numbers to widgets such as label and text. One thread is triggered by button (click event) and second is executed as a background thread.
import Tkinter as tk
from ttk import *
from Tkconstants import *
import threading, thread, time
def tl1(text,counter):
while True:
text.insert(END,counter)
counter += 1
time.sleep(2)
def tl2(label,counter):
while True:
label['text'] = counter
counter += 1
time.sleep(1)
class mainWindow():
def __init__(self, master):
self.master = master
self._initLayout()
def _initLayout(self):
#button
self.button = tk.Button(self.master, text="thread1_start", command = self._task1)
self.button.pack()
#label
self.label = tk.Label(self.master)
self.label.pack()
#text
self.text = tk.Text(self.master, width=30)
self.text.pack()
def _task1(self):
t1 = thread.start_new_thread(tl1,(self.text,1))
def _task2(self):
t2 = thread.start_new_thread(tl2,(self.label,1000))
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.mainWindow = mainWindow(self)
self.mainWindow._task2() #background_thread
app = App()
app.mainloop()
In this manner everything works fine, but if we change the background thread to display results on text widget, the whole app freezes.
Why background thread works fine communicating with label but causes problems with text widget? Is there any way to run it properly?
Tkinter isn't thread safe. You can only access widgets from the thread that created them. Your threads will need to put data on a thread-safe queue, and your GUI thread will need to poll the queue.
In your particular case you don't need threads at all. You can use the tkinter after method to run code periodically.

Tkinter: Calling a multithreaded instance

I am a beginner at python, and it is my first language. I have been tasked with something that is quickly becoming too large for me to grasp that I need to finish. I am almost done. At this moment, I have created a dialog box that serves as a Main Menu, a dialog box that is a selectable option from the main menu that runs a test, and a multithreaded instance which runs the test, opens up a "please wait" box and up on finishing the test, another dialog box pops up which declares the test complete.
My issue: Within the "Run Test" dialog, I am trying to create a button that will call the multithreaded instance into action. From the code I have parsed together with the help of others, I can not see which class to instantiate within the "Run Test" dialog box.
I am beginning to believe my implementation of threading is incorrect. However there must be a way.
This is the module I am trying to call on.
from slice_setup import SLICE_SETUP
import Tkinter as tk
import threading
import Queue
class GuiPart:
def __init__(self, master, queue):
self.queue = queue
self.master = master
self.master.geometry("300x100+400+250")
self.master.title("RSAM BCT")
tk.Label(master, text="REDCOM SLICE", fg="red").pack()
tk.Label(master, text="BCT - Basic Configuration Test", fg= "red").pack()
tk.Label(master, text="Please wait...", fg= "black").pack()
tk.Label(master, text="Estimated time: 3 min 6 sec", fg= "black").pack()
def processIncoming(self):
while self.queue.qsize():
try:
text = self.queue.get(0)
Complete(self.master, text)
except Queue.Empty:
pass
class ThreadedClient:
def __init__(self, master):
self.master = master
self.queue = Queue.Queue()
self.gui = GuiPart(master, self.queue)
self.running = True
self.thread = threading.Thread(target=self.workerThread1)
self.thread.start()
self.periodicCall()
def periodicCall(self):
self.gui.processIncoming()
if not self.running:
return
self.master.after(100, self.periodicCall)
def workerThread1(self):
obj_rcs = SLICE_SETUP()
obj_rcs.SLICE()
self.queue.put("Configuration Complete!")
self.running = False
class Complete(tk.Toplevel):
def __init__(self, master=None, completetext=""):
tk.Toplevel.__init__(self, master)
self.geometry("400x300+400+250")
self.title("RSAM BCT")
tk.Label(self, text="REDCOME SLICE", fg="red").pack()
tk.Label(self, text="BCT - Basic Configuration Test", fg="red").pack()
tk.Label(self, text=completetext, fg="dark green").pack()
tk.Label(self, text="Trunk 1: Port 1: Phone 1: 760-450-4500", fg="black").pack()
tk.Label(self, text="Trunk 1: Port 2: Phone 2: 760-450-4501", fg="black").pack()
tk.Button(self, text=" Exit ", command=self.destroy).pack()
if __name__ == "__main__":
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
and this is where I am trying to call from:
import sys
import Tkinter as Tk()
from bct_pleasewait import ????
import threading
import Queue
import time
sGui = Tk()
class slice_menu:
def runtest(self):
obj_wait = ????
obj_wait.????
def slicemenu(self):
sGui.geometry("400x300+400+250")
sGui.title("RSAM BCT")
Label(sGui, text= "REDCOM SLICE", fg="red").pack()
Label(sGui, text= "BCT - Basic Configuration Test", fg= "red").pack()
Label(sGui, text= "-Ensure you are logged off of HyperTerminal", fg= "black").pack()
Label(sGui, text= "-Turn on your REDCOM SLICE unit",
fg= "black").pack()
Label(sGui, text= "-Please connect your laptop to SLICE CONSOLE", fg= "black").pack()
Label(sGui, text= "-This configuration will take 3 minutes", fg= "black").pack()
Button(sGui, text = " Run ", command = self.runtest).pack()
Button(sGui, text = " Exit test ", command = sGui.destroy).pack()
sGui.mainloop()
This class still has minor errors in it but I just want to get this issue solved first.
Not a specific answer, but your question is very broad.
Some points to keep in mind:
Tk is not thread-safe. That is, you should only invoke Tk calls from the main thread. It is OK to let other threads do non-gui work.
In CPython, only one thread at a time can be executing Python bytecode, due to the Global Interpreter Lock ("GIL"). So your non-gui thread could make the GUI unresponsive.
If you are only running one test at a time, and if you can divide that test into small pieces, you could use a timeout (also called alarm handler). This handler does a little bit of work, saves its state, updates the progress dialog and then exits, waiting to be invoked again.
If the test runs a long time and you want the GUI to remain responsive, I would suggest using multiprocessing instead of threading. Start your test in a different process, and use things like Queues and Semaphores et cetera to communicate between the GUI and non-qui processes.

Categories

Resources