user5494969
user5494969

Reputation: 191

How can I use threading to update a text field in PyQt while the gui is open?

I have a text field on the PyQt GUI that is set to print text from another file, however that file can change somewhat often so I want the textfield to update every 10 seconds so that the user can see the live file text. Here is a watered down version of what I am working with, however the threading does not work: The /tmp/py.state.test file is being written too continuously, so I want that change to appear on the QTextBrowser. As it is now, if I run the program it will get initial the text correctly, but it does not update while it runs.

from PyQt4 import QtGui, QtCore
from PyQt4.Qt import *
import sys, os, threading, time

class TestWindow(Qt.Gui.QWidget):

    def __init__(self):
        super(TASwitchWindow, self).__init__()
        self.initUI()

    def initUI(self):
        t=threading.Thread(target.self.cfglabel())
        t.start()
        t.join()
        self.setGeometry(200,200,800,600)
        self.setWindowTitle('TA Switch Tab')
        self.show()

    def cfglabel(self):
        self.lbl = QtGui.QTextBrowser(self)
        self.lbl.resize(760,40)
        self.lbl.move(20,440)
        with open ("/tmp/py.state.test", "r") as myfile:
            data=myfile.read().replace('\n', '')
        self.lbl.append(data)
        time.sleep(10)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = TestWindow()
    sys.exit(app.exec_())

Apologies, there may be typos as my code is on another system and I had to retype it. If someone could tell me how to update the text every 10 seconds, that would be great. Also would it be better to use a QLabel or something else instead of a TextBrowser? Thanks

Upvotes: 0

Views: 4161

Answers (2)

justengel
justengel

Reputation: 6320

Sounds like you really want to just implement a timer. QTimers are really easy to use.

from PyQt4 import QtGui, QtCore
import sys, os

class TestWindow(QtGui.QWidget):

    def __init__(self):
        super(TestWindow, self).__init__()

        self.setGeometry(200,200,800,600)
        self.setWindowTitle('TA Switch Tab')
        self.lbl = QtGui.QTextBrowser(self)
        self.lbl.resize(760,40) # Typically you create a layout and put the widget in the layout.
        self.lbl.move(20,440)

        self._update_timer = QtCore.QTimer()
        self._update_timer.timeout.connect(self.update_label)
        self._update_timer.start(10000) # milliseconds

    def update_label(self):
        with open ("/tmp/py.state.test", "r") as myfile:
            data=myfile.read().replace('\n', '')
        self.lbl.append(data)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = TestWindow()
    win.show()
    sys.exit(app.exec_())

I also made a library for this. See https://pypi.org/project/qt-thread-updater/

Upvotes: 1

three_pineapples
three_pineapples

Reputation: 11869

There are a few problems with your code that will be preventing it from working correctly.

  1. You are updating the GUI from a thread. This is forbidden in Qt. All interaction with GUI objects must be done from the main thread or else your program will randomly crash. You need to instead use a QThread and emit a signal from your QThread to a slot in the main thread which then interacts with the GUI objects (signal emission is thread safe, pretty much everything else is not).

  2. When you instantiate your Python thread you are not passing a reference to the method you want to run in the thread. You have written t=threading.Thread(target.self.cfglabel()). Note the extra brackets after cfglabel. What you have actually done is run the cfglabel method in the main thread, and used it's return value (in your case None) as the method that should be run in the thread. Obviously this doesn't work! The line of code should instead read t=threading.Thread(target.self.cfglabel)

  3. You wait for the thread to finish (t.join()) before your application starts running. So of course the thread is never running in the background to constantly update the GUI. You don't want to join the thread in your use case.

  4. The change in #3 raised above requires that you save a reference to the thread so that it is not garbage collected once the initUI method finished running. I suggest storing it as an instance attribute: aka self.t.

  5. The code that reload the file is not in a while loop. So currently it would only load the file once, not reload every 10 seconds. You don't want the cfglabel method to ever end (when it ends, so does the thread and you won't get any more updating). So you would want the code to become something like:

_

def cfglabel(self):
    self.lbl = QtGui.QTextBrowser(self)
    self.lbl.resize(760,40)
    self.lbl.move(20,440)

    while True:
        self.lbl.clear()
        with open ("/tmp/py.state.test", "r") as myfile:
            data=myfile.read().replace('\n', '')
        self.lbl.append(data)
        time.sleep(10)

Note that the above code has not taken into account the required modification raised in point #1 above, and will likely cause your program to crash. Everything to do with self.lbl needs to be moved into the main thread, and you need to use a QThread so you can define and emit a custom signal.

Hopefully this helps with future threading endeavours, but in this case, going with a QTimer is the simplest option provided that the reading of the text file does not become a bottleneck.

Upvotes: 2

Related Questions