Tkinter program runs function but the window pops up after it ends - python

Ok so I'm pretty new to tkinter and I can not solve this problem that I am having.
When I run the program, it runs the function and after it ends the window with the photo pops up, but the loop does not start the program again.
import tkinter as tk
from tkinter import*
from PIL import ImageTk,Image
from tkinter import messagebox
import YuaChanMainFunc
import time
def on_click(event=None):
# `command=` calls function without argument
# `bind` calls function with one argument
print("Hey Yua!")
YuaChanMainFunc.statement="hey yua"
class Window(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
master.title("Yua-chan AI")
self.img = ImageTk.PhotoImage(Image.open("YuaChanAI/Yua Chan Artwork/YuaChan2.png"))
MainLB = tk.Label(master, image=self.img)
MainLB.bind('<Button-1>', on_click)
MainLB.pack()
b = tk.Button(root, text="Close", command=root.destroy)
b.pack()
#YuaChanMainFunc.YuaChanAIMainFunc()
root = tk.Tk()
#instance of the class
app = Window(root)
root.resizable(0,0)
root.geometry("310x500+1600+510")
YuaChanMainFunc.YuaChanAIMainFunc()
#Runs the application until we close
root.mainloop()

From what I understand you want "YuaChanMainFunc.YuaChanAIMainFunc()"
to run in the background while UI runs the foreground. For that you can start the "YuaChanMainFunc.YuaChanAIMainFunc()" in a different thread and run UI in the main thread itself. Now you can make "YuaChanMainFunc.YuaChanAIMainFunc()" an infinite loop and it wont block root.mainloop(). Also keep in mind root.mainloop() is also an infinite loop. SO anything you write after that will not execute until you close the program.
import threading
backend_thread = threading.Thread(target=YuaChanMainFunc.YuaChanAIMainFunc, args=())
backend_thread.daemon = True #This will make sure backend thread closes when you close ui
backend_thread.start()
root.mainloop()

Related

How to trigger again a callback function before the first call for that function has ended

Good morning,
What I am aiming for:
I want to create a button that when clicked triggers a call back function (even if that function is still running).
Module:
I am using tkinter
Problem:
When I trigger the button, the function runs, but then I cannot push the button again before the function has finished its run. I want to be able to push the button while the function is still running and have the function stop and run again from start.
My attempts:
I tried both a procedural and a OOP approach: both present the same problem
My attempt n1: Procedural approach
import time
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
def func():
for i in range (100):
print(i)
time.sleep(5)
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=func)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
My attempt n2: OOP approach
import time
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
class OOP():
def func(self):
for i in range (100):
print(i)
time.sleep(5)
def __init__(self):
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=self.func)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
oop = OOP()
Thanks
In tkinter, as with most GUI frameworks, there is a for loop running somewhere that constantly checks for user input. You are hijacking that loop to do your func processing. Tkinter can not check for user input, nor e.g. change the appearance of the button icon, because there is only one thread and you are using it.
What you will want to do is fire up a thread or process to do the work. There are lots of libraries to do parallel processing in elaborate ways if you have lots of background tasks but this will do for a single function (see also this answer).
import time
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
import threading
def button():
thread = threading.Thread(target=func, args=args)
thread.start()
def func():
for i in range (100):
print(i)
time.sleep(5)
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=button)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
If the process is long running, you might need to find a way to clean up the threads when they're done.

Tkinter Threading Error: RuntimeError: threads can only be started once

I have created a tkinter GUI in the following structure:
import tkinter as tk
import threading
class App:
def __init__(self, master):
self.display_button_entry(master)
def setup_window(self, master):
self.f = tk.Frame(master, height=480, width=640, padx=10, pady=12)
self.f.pack_propagate(0)
def display_button_entry(self, master):
self.setup_window(master)
v = tk.StringVar()
self.e = tk.Entry(self.f, textvariable=v)
buttonA = tk.Button(self.f, text="Cancel", command=self.cancelbutton)
buttonB = tk.Button(self.f, text="OK", command=threading.Thread(target=self.okbutton).start)
self.e.pack()
buttonA.pack()
buttonB.pack()
self.f.pack()
def cancelbutton(self):
print(self.e.get())
self.f.destroy()
def okbutton(self):
print(self.e.get())
def main():
root = tk.Tk()
root.title('ButtonEntryCombo')
root.resizable(width=tk.NO, height=tk.NO)
app = App(root)
root.mainloop()
main()
I want to prevent the GUI from freezing when running a function (in the example code it's the function of the ok-button). For that I found the solution of using the thread-module as best practice. But the problem is that when I want to run the code once again, python returns this traceback:
RuntimeError: threads can only be started once
I'm totally aware of the problem that threads can be only starten once as stated in the error message. My question is: How can I stop a thread to start it a second time or does anybody has a better workaround for preventing the GUI from freezing and pressing a button/running a function multiple times?
BR and thank you
Lorenz
Your code will only create one thread and assign its start function reference to command option. Therefore same start() function will be called whenever the button is clicked.
You can use lambda instead:
command=lambda: threading.Thread(target=self.okbutton).start()
Then whenever the button is clicked, a new thread will be created and started.

destroy a tkinter window an proceed further

I created a tkinter Toplevel window for my application and later in the program destroyed it but after destroying the window the program doesnt get executed further and get struck there itself doing nothing . Here is the code that I used :-
#login.py
from tkinter import *
class gui:
def __init__(self):
#does something
def login(self):
self.winLogin.destroy()
def guilogin(self):
self.winLogin = Toplevel()
btn = Button(self.winLogin,command=self.login,text='asd')
btn.pack()
self.winLogin.mainloop()
#main.py
import login
from tkinter import *
main = Tk()
a = login.gui()
a.guilogin()
if True:
#some code and this part doesnot get executed
main.mainloop()
else:
main.destroy()
I run main.py file and the code get struck and do nothing before the if part . I tottaly have no idea whats wrong . Pls. Help!
As furas said in the comments, you should not call mainloop on the toplevel, instead use grab_set to disable the main window and wait_window to wait for the toplevel to be closed:
from tkinter import Tk, Toplevel, Button
def login():
top = Toplevel(root)
Button(top, text="Quit", command=top.destroy).pack()
top.grab_set() # deactivate the main GUI while top is opened
root.wait_window(top) # wait for top to be closed before doing the rest
print("logged in")
root = Tk()
Button(root, text="login", command=login).pack()
root.mainloop()

Why is threading not showing Tkinter window?

I wrote some code while learning to use Tkinter and when i attempted to thread it, it did not show the window however when it runs just the main loop it does.
import socket,threading,time
from Tkinter import *
class Chat(Frame):
def __init__(self,root):
Frame.__init__(self,root)
self.text=Text(self, bg='black', fg='white')
self.text.configure(state=DISABLED)
self.text.configure(state=NORMAL)
self.text.insert(END, 'hello\n'*40)
self.text.configure(state=DISABLED)
self.text.pack()
def main():
root=Tk()
root.configure(background='black')
c=Chat(root)
c.pack()
root.mainloop()
#t=threading.Thread(target=root.mainloop)
#t.start()
if __name__=='__main__':
main()
It seems to be a problem with the text widget but i don't know what is wrong with it. When i remove the insert line, the box appears with trheading but with that line, it does not appear. What is the problem with it?
I think your problem is that you are initialising Tkinter on the mainthread and then invoking the Chat frame (which uses root from the mainthread) on the background thread. I expected this might cause some problems. Without having much knowledge of the internals I decided to test this theory out by writing your code slightly differently. I have re-written your code so the initialisation of root and Chat is on the same thread and it does the trick.
import threading
from Tkinter import *
class Chat(Frame):
def __init__(self,root):
Frame.__init__(self,root)
self.text=Text(self, bg='black', fg='white')
self.text.configure(state=DISABLED)
self.text.configure(state=NORMAL)
self.text.insert(END, 'hello\n'*40)
self.text.configure(state=DISABLED)
self.text.pack()
def run():
root=Tk()
root.configure(background='black')
c=Chat(root)
c.pack()
root.mainloop()
def main():
t=threading.Thread(target=run)
t.start()
t.join()
if __name__=='__main__':
main()
Hope that helps.

Threads and tkinter

I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
When I click in the close button (in the upper right corner) the following error appears in the console:
Tcl_AsyncDelete: async handler deleted by the wrong thread
I don't know why it happens or what it means.
All Tcl commands need to originate from the same thread. Due to tkinter's
dependence on Tcl, it's generally necessary to make all tkinter gui statements
originate from the same thread. The problem occurs because
mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.
The problem can be avoided by not making mainWindow an attribute of tkinterGui
-- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
If you want to define the GUI inside a class, you can still do so:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
or even simpler, just use the main thread to run the GUI mainloop:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
The fix here is simple, but hard to discover:
Call mainWindow.quit() immediately after mainwindow.mainloop(), so that the cleanup happens on the same thread as the one that created the tk UI, rather than on the main thread when python exits.

Categories