Infinite loop in Tkinter python code - python

I'm trying to run a simple Tkinter program that opens a program when you click a button. The code is listed below. I use a command to call a program that then calls a fortran program. However, when I click on the button, it opens the program but the menu of the program i'm calling goes into an infinite loop......the offending code seems to be in the button1Click module.
Any help is greatly appreciated.
Thanks
from Tkinter import *
import os, sys
from win32com.client import Dispatch
xlApp=Dispatch('Excel.Application')
_PSSBINPATH=r"C:\Program Files\PTI\PSSE32\PSSBIN"
os.environ['PATH']=_PSSBINPATH+';'+os.environ['PATH']
sys.path.insert(0,_PSSBINPATH)
import redirect; redirect.psse2py()
import psspy
class MyApp:
def __init__(self, parent):
self.myParent = parent ### (7) remember my parent, the root
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.bind("<Button-1>", self.button1Click) ### (1)
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Cancel", background="red")
self.button2.pack(side=RIGHT)
self.button2.bind("<Button-1>", self.button2Click) ### (2)
def button1Click(self,event): ### (3)
psspy.runiplanfile(r"C:\MNTACT\Contingency Program\work\contingency-31-4.irf")
if self.button1["background"] == "green": ### (4)
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self, event): ### (5)
self.myParent.destroy() ### (6)
root = Tk()
myapp = MyApp(root)
root.mainloop()

What makes you think there's an infinite loop happening? I see no loop in button1Click, unless the loop is in runiplanfile. Are you using "infinite loop" to mean simply that the GUI has stopped responding?
Tkinter is single threaded and cannot process events except via the event loop. If one event takes a long time to process, the GUI will hang until the processing of that event is completed. If you're exec'ing an external process and waiting for it to complete, your GUI will appear to be frozen until that process finishes.

Related

How can I terminate a tkinter mainloop from a different thread

I have the following problem: I would like to create a GUI with tkinter, that reacts to signals, sent from a socket. For example, I would like to be able to terminate the application, when an end signal is received.
For that purpose I have a function, running in a separate thread, that listens for signals and acts accordingly. However, when I try to destroy the tkinter-GUI, the programm stops, and gives this error message:
Fatal Python error: PyEval_RestoreThread: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL)
Python runtime state: initialized
I have recreated this minimum working example giving the same behavior:
import tkinter as tk
import time
import threading
class Gui(tk.Frame):
"""Minimal GUI with only a button"""
def __init__(self, master: tk.Tk):
tk.Frame.__init__(self, master)
self.pack()
tk.Button(self, text='Spam').pack()
class Client:
"""Client for handling signals"""
def __init__(self, master: tk.Tk):
self.master = master
self.gui = Gui(self.master)
self.signal = None # Initialize signal
self.thread = threading.Thread(target=self.listen_thread)
self.running = True
self.thread.start()
def listen_thread(self):
"""Listen for signals and handle actions"""
while self.running:
signal = self.signal # Dummy signal, set by external method, instead of received message from socket
if signal == 'end': # End signal received
self.master.destroy() # Destroy tkinter GUI, error occurs here
self.running = False # Terminate while loop
else:
time.sleep(0.2)
def send_signal_after(receiver: Client, delay: float = 2.0):
"""Send a signal to the client after short delay"""
time.sleep(delay)
receiver.signal = 'end'
if __name__ == '__main__':
root = tk.Tk()
client = Client(root)
threading.Thread(target=send_signal_after, args=(client,)).start()
root.mainloop()
if client.thread: # Check if thread is still running, if so, wait for termination
client.thread.join()
I am running this on MacOS 12.1, Python 3.10.
Is there any other way to terminate the application? I know, I could probably use sys.exit(), but I would like to do this in a cleaner way.
Thank you!
So, to understand how to do it, I made an example:
This is the first file (the main one) :
import tkinter as tk
from tkinter import *
import threading
import file2 as file2
def func(gui):
# just some code around here
# start up the program
root = Tk()
# pass the root in the __init__ function from file2
mainGui = file2.file2Class(root)
# Start the new thread
theThread = threading.Thread(target=func, args=([mainGui]))
theThread.daemon = True
theThread.start()
# loop command
tk.mainloop()
And this is file2 :
import tkinter as tk
from tkinter import *
class file2Class:
root = None
def __init__(self, initialRoot):
self.root = initialRoot
self.createUI();
def createUI(self):
# This is an exit button
tk.Button(
self.root,
text="Exit",
font = "Verdana 10 bold",
fg="red",
command=self.root.destroy, # <- this is the exit command
width=25,
height=2).grid(row=0,column=0)
So, the most important thing is to make sure that you pass root in the args of the thread.
I hope I helped you!

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

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()

Passing variables into a tkinter class window

I want to create a GUI in tkinter that not only executes commands when a button is pressed, but responds to the state of a larger script running in a separate thread.
I have really dug around and tried to find some information on message passing, and I have found some great info on the pickle module, using multiprocessing and its built in tools and also threading, and queuing. I have even dug into David Beazley's lesson on concurrency located here. I just can't get the syntax right on any of those methods.
I have broken down my code into a small functional unit that should launch a little tkinter window like this:
tkinter window
The code below has a "launchGUI" function that launches my tkinter GUI, a "myLoop" function that starts the threads and will also loop to drive my larger program later, right now it just rotates the blink variable. I also have a blinkCheck method in my class that checks the status of the blink variable in the class.
I don't know if I am even putting my message receiver in the right place. In the following example code I am just trying to pass a global variable into the class. I know it is getting into the class, because the blinkCheck() method works even though uncommenting that method crashes the window. However, with the method turned off the label in the GUI never changes. I think the window crashing is the least of my worries, it must be because i have another while loop running.
What is the correct way to get that number in Label to change?
Here is my example code:
import tkinter as tk
from tkinter import Frame, Label
import time
import threading
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.label.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def launchGUI():
root = tk.Tk()
root.title("My Blinker")
app1 = MyClass(root)
app1.mainloop()
def myLoop():
global blink
t1=threading.Thread(target=launchGUI)
t1.daemon = True
t1.start()
print("blink in blinker function is {}".format(blink))
while True:
if blink == 0:
blink = 1
else:
if blink == 1:
blink = 0
time.sleep(2.5)
if __name__=="__main__":
myLoop()
In your description you have mentioned something about involving buttons. I do not see that in your provided snippet. But with buttons it is possible to configure the label, i.e:
from tkinter import Label, Button
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.button = Button(master, text="Button", command=lambda: foo(self.label))
self.label.pack()
self.button.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def foo(self, label):
label.config(text=blink)
Conventionally, this would be the most simple way to configure a label within an active thread.
If anyone feels like this answer may not be fully correct, please do edit it because I am new to Stack Overflow!
First, the GUI must run in main thread, and must not blocked by a infinite loop. Use after instead. To communicate, use some appropriate object from threading, e.g. Event:
import tkinter as tk
import time
import threading
class MyClass(tk.Frame):
def __init__(self, master, event):
super().__init__(master)
self.master = master
self.event = event
self.label = tk.Label(master, text='')
self.label.pack()
self.after(100, self.blink_check)
def blink_check(self):
self.label['text'] = self.event.is_set()
self.after(100, self.blink_check)
def blink(event):
while True:
event.set()
time.sleep(2.5)
event.clear()
time.sleep(2.5)
def main():
root = tk.Tk()
root.title("My Blinker")
event = threading.Event()
t = threading.Thread(target=blink, args=(event,))
t.daemon = True
t.start()
frame = MyClass(root, event)
root.mainloop()
if __name__=="__main__":
main()

wxPython popup from calling imported function

I have a GUI made with wxPython that calls a function that I imported from a separate Python file when I press a button, and shows the output of that function in a text box. I want to improve it so that if the function asks for user input mid-execution (like a raw_input()), I want a new popup window to appear instead of the raw_input waiting in the text box. I've been looking through the wxPython documentation but can't seem to find anything that resembles what I want, so I was wondering if anyone here could give me any pointers.
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
cont = raw_input("Something has gone wrong. Do you still want to continue?")
if(cont.lower() == "n")
sys.exit(0)
#more function code...
print "Success!"
I would call the external function via a button event. Instead of raw_input, I would just use a wx.MessageDialog with a yes or no button on it. You can check which button the user pressed and continue or not accordingly. Here are some links on that dialog and others:
http://wxpython.org/Phoenix/docs/html/MessageDialog.html
http://www.blog.pythonlibrary.org/2010/06/26/the-dialogs-of-wxpython-part-1-of-2/
http://zetcode.com/wxpython/dialogs/
If this piece of code you are running takes a long time (i.e. greater than a second), then it is probably going to block wx's mainloop and cause the application to become unresponsive. If that is the case, then you'll need to move this code into a thread. The following articles will help you with that course of action:
http://wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/

Stopping a python thread from a tkinter gui

I'm trying to create a simple Python GUI (with Tkinter) with start button, running a while loop in a thread, and a stop button to stop the while loop.
I'm having issue with the stop button, which doesn't stop anything and frozen GUI once the start button is clicked.
See code below:
import threading
import Tkinter
class MyJob(threading.Thread):
def __init__(self):
super(MyJob, self).__init__()
self._stop = threading.Event()
def stop(self):
self._stop.set()
def run(self):
while not self._stop.isSet():
print "-"
if __name__ == "__main__":
top = Tkinter.Tk()
myJob = MyJob()
def startCallBack():
myJob.run()
start_button = Tkinter.Button(top,text="start", command=startCallBack)
start_button.pack()
def stopCallBack():
myJob.stop()
stop_button = Tkinter.Button(top,text="stop", command=stopCallBack)
stop_button.pack()
top.mainloop()
Any idea how to solve this? I'm sure this is trivial and must have be done thousands of times but I cannot find a solution myself.
Thanks
David
The code is calling run method directly. It will call the method in the main thread. To run it in a separated thread you should use threading.Thread.start method.
def startCallBack():
myJob.start()

Categories