0

I have a simple program with two classes, one controls a relay board via a serial.serial connection. The other class is for a GUI which will send commands to the relay class and then display the status of the relay board.

I'm having an issue with sending messages from the relay class to the tkinter class. The messages only appear once the relay command has finished. I've cut down my program below. Test.test() represents a function in my relay class where as the MainWindow class is my GUI.

Someone has pointed out to use threading to handle the messages being passed between the classes. Is that my only option? I have not delved into threading yet.

from Tkinter import *
import time
import ScrolledText

class Test():
     def test(self):
        main.textboxupdate(" test start ")
        time.sleep(2)
        main.textboxupdate(" test middle ")
        time.sleep(2)
        main.textboxupdate(" test end ")

class MainWindow(Frame):
    def __init__(self, *args, **kwargs):
        Frame.__init__(self, *args, **kwargs)       
        self.canvas = Canvas(width=1200,height=700)
        self.canvas.pack(expand=YES,fill=BOTH)
        self.frame = Frame(self.canvas)
        self.TextBox = ScrolledText.ScrolledText(self.frame) 
        self.open = Button(self.frame, text="Open Cover",
                            command=test.test)

    def createtextbox(self, statusmsg):
        self.frame.place(x=0,y=0)
        self.TextBox.config(state = NORMAL)
        self.TextBox.insert(END, statusmsg,('error'))
        self.TextBox.config(state = 'disabled', height = 2, width = 35)
        self.TextBox.see(END)
        self.TextBox.grid(columnspan=2, rowspan = 1)
        self.open.grid()

    def textboxupdate(self, statusmsg):
        statusmsg =  statusmsg +'\n'
        self.TextBox.config(state = NORMAL)
        self.TextBox.insert(END, statusmsg,('error'))
        self.TextBox.config(state = 'disabled', height = 10, width = 50)
        self.TextBox.see(END)  

test = Test()        
root = Tk()  
main = MainWindow(root)
main.createtextbox('Startup\n')
root.mainloop()
matsjoyce
  • 5,744
  • 6
  • 31
  • 38
Oman
  • 23
  • 4
  • So a friend just solved this for me. I needed self.update_idletasks() at the end of the MainWindow.textboxupdate() function. – Oman Apr 09 '15 at 14:07
  • That isn't a fix, that's a workaround. Calling `sleep` in a gui is fundamentally flawed. – Bryan Oakley Apr 09 '15 at 15:35

1 Answers1

0

Here's one option:

from Tkinter import *
import time
import ScrolledText
import threading, Queue

class Test():
    def __init__(self):
        self.msg_queue = Queue.Queue()

    def test(self):
        self.msg_queue.put(" test start ")
        time.sleep(2)
        self.msg_queue.put(" test middle ")
        time.sleep(2)
        self.msg_queue.put(" test end ")

class MainWindow(Frame):
    def __init__(self, *args, **kwargs):
        Frame.__init__(self, *args, **kwargs)       
        self.canvas = Canvas(width=1200,height=700)
        self.canvas.pack(expand=YES,fill=BOTH)
        self.frame = Frame(self.canvas)
        self.TextBox = ScrolledText.ScrolledText(self.frame) 
        self.open = Button(self.frame, text="Open Cover",
                            command=self.create_thread)
        self.test_thread = None
        self.createtextbox("")

    def create_thread(self):
        self.test_thread = threading.Thread(target=test.test)
        self.test_thread.start()
        self.after(10, self.update_textbox)

    def update_textbox(self):
        while not test.msg_queue.empty():
            self.textboxupdate(test.msg_queue.get())
        if self.test_thread.is_alive():
            self.after(10, self.update_textbox)
        else:
            self.test_thread = None


    def createtextbox(self, statusmsg):
        self.frame.place(x=0,y=0)
        self.TextBox.config(state = NORMAL)
        self.TextBox.insert(END, statusmsg,('error'))
        self.TextBox.config(state = 'disabled', height = 2, width = 35)
        self.TextBox.see(END)
        self.TextBox.grid(columnspan=2, rowspan = 1)
        self.open.grid()

    def textboxupdate(self, statusmsg):
        statusmsg =  statusmsg +'\n'
        self.TextBox.config(state = NORMAL)
        self.TextBox.insert(END, statusmsg,('error'))
        self.TextBox.config(state = 'disabled', height = 10, width = 50)
        self.TextBox.see(END)  
        self.update_idletasks()

test = Test()
main = MainWindow()
main.pack()
main.mainloop()

The first change is that instead of calling a function, Test.test puts the messages onto a queue. Test.test is started in a separate thread by MainWindow.start_thread. MainWindow.start_thread also schedules a check on the thread by asking tkinter to call update_textbox after 10 milliseconds (self.after(10, self.update_textbox)). This function takes all the new messages off the queue, and displays them. Then, if the thread is still running, it reschedules itself, otherwise it resets the MainWindow.

matsjoyce
  • 5,744
  • 6
  • 31
  • 38