Update QlistView with python list updated from another thread (pyqt5)
Question: I try to create a GUI for displaying a python a list of 512 values 0/255

It's simple with PyQt to setup a QListWidget or QListView to display this kind
of list

    
    
    from sys import argv, exit
    from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QApplication
    
    
    class Universe(QListWidget):
        """This is a class for a DMX Universe of 512 dimmers"""
        def __init__(self):
            super(Universe, self).__init__()
            for i in range(512):
                item = QListWidgetItem('dimmer n° '+str(i+1)+' : 0')
                self.addItem(item)
    
    if __name__ == "__main__":
      app = QApplication(argv)
      list_widget = Universe()
      list_widget.show()
      exit(app.exec_())
    

I can create a button to send random values. No latency, everything is nice.

    
    
        from random import randrange
        from sys import argv, exit
        from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QApplication, QGroupBox, QVBoxLayout, QPushButton
    
    
        class DisplayGroup(QGroupBox):
            """This is a group of widgets to display"""
            def __init__(self):
                super(DisplayGroup, self).__init__()
                # create a vertical layout
                vbox = QVBoxLayout()
                # create an universeand and add it to the layout
                universe = Universe()
                self.universe = universe
                vbox.addWidget(universe)
                # create a button to make some noise
                button = QPushButton('make some noise')
                vbox.addWidget(button)
                button.released.connect(self.make_some_noise)
                # set the layout on the groupbox
                vbox.addStretch(1)
                self.setLayout(vbox)
    
            def make_some_noise(self):
                self.universe.update([randrange(0, 101, 2) for i in range(512)])
    
    
        class Universe(QListWidget):
            """This is a class for a DMX Universe of 512 dimmers"""
            def __init__(self):
                super(Universe, self).__init__()
                for index in range(512):
                    item = QListWidgetItem('dimmer n° '+str(index+1)+' : 0')
                    self.addItem(item)
    
            def update(self, data):
                for index, value in enumerate(data):
                    item = self.item(index)
                    item.setText('dimmer n° '+str(index+1)+' : '+str(value))
    
    
    
        if __name__ == "__main__":
          app = QApplication(argv)
          group_widget = DisplayGroup()
          group_widget.show()
          exit(app.exec_())
    

My problem is that the lib I use for listening new frames need to be in a
separate thread. I create a thread to listen updates for the list. But I
cannot managed to have my QListView / QListWidget updated each time a value
changed. The Widgets are only updated when I click on the widget itself.

I try everything I found on forums, but I cannot managed to make it working. I
try to use signal for dataChanged.emit and even a (ugly) global. But the value
is not updated on my view.

Here is the latest code with the ugly global.

If anyone could help me on this point.

cheers !!

    
    
        from random import randrange
        from time import sleep
        from sys import argv, exit
        from PyQt5.QtCore import QThread, QAbstractListModel, Qt, QVariant
        from PyQt5.QtWidgets import QListView, QApplication, QGroupBox, QVBoxLayout, QPushButton
    
        universe_1 = [0 for i in range(512)]
    
        class SpecialProcess(QThread):
            def __init__(self):
                super(SpecialProcess, self).__init__()
                self.start()
    
            def run(self):
                global universe_1
                universe_1 = ([randrange(0, 101, 2) for i in range(512)])
                sleep(0.1)
                self.run()
    
    
        class Universe(QAbstractListModel):
            def __init__(self, parent=None):
                super(Universe, self).__init__(parent)
    
            def rowCount(self, index):
                return len(universe_1)
    
            def data(self, index, role=Qt.DisplayRole):
                index = index.row()
                if role == Qt.DisplayRole:
                    try:
                        return universe_1[index]
                    except IndexError:
                        return QVariant()
                return QVariant()
    
    
        class Viewer(QGroupBox):
            def __init__(self):
                super(Viewer, self).__init__()
                list_view = QListView()
                self.list_view = list_view
                # create a vertical layout
                vbox = QVBoxLayout()
                universe = Universe()
                vbox.addWidget(list_view)
                # Model and View setup
                self.model = Universe(self)
                self.list_view.setModel(self.model)
                # meke a process running in parallel 
                my_process = SpecialProcess()
                # set the layout on the groupbox
                vbox.addStretch(1)
                self.setLayout(vbox)
    
    
        if __name__ == "__main__":
          app = QApplication(argv)
          group_widget = Viewer()
          group_widget.show()
          exit(app.exec_())
    


Answer: You need to tell the model that the underlying data has changed so that it can
update the view. This can be done with a custom signal, like this:

    
    
    from PyQt5.QtCore import pyqtSignal
    
    universe_1 = [0 for i in range(512)]
    
    class SpecialProcess(QThread):
        universeChanged = pyqtSignal()
    
        def __init__(self):
            super(SpecialProcess, self).__init__()
            self.start()
    
        def run(self):
            universe_1[:] = [randrange(0, 101, 2) for i in range(512)]
            self.universeChanged.emit()
            sleep(0.1)
            self.run()
    

Which is then connected to the model like this:

    
    
        my_process = SpecialProcess()
        my_process.universeChanged.connect(self.model.layoutChanged.emit)
    

