0

I have 2 QListWidget lists, List2 is being filled when some item has been selected from List1QListWidgets

The problem is that, before filling List2 i have to do alot of tasks which freezes my UI for about 5 seconds which is too annoying, i want to make it fill List2 with QThread but it's not working since before initilizing whole class I'm getting an annoying error

from ui import Ui_Win
from PyQt4 import QtGui, QtCore

class GenericThread(QtCore.QThread):
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

    def __del__(self):
        self.quit()
        self.wait()

    def run(self):
        self.emit( QtCore.SIGNAL('itemSelectionChanged()'))
        return

class MainUI(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self)
        self.ui = Ui_Win()
        self.ui.setupUi(self)
        ...

        genericThread = GenericThread(self)
        self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
        genericThread.start()

    def fill_List2(self):
        self.ui.List2.clear()
        list1SelectedItem = str(self.ui.List1.currentItem().text()) # ERROR HERE

Traceback: # AttributeError: 'NoneType' object has no attribute 'text'

This accures because self.ui.List1.currentItem().text() is None

Why this fucntion is being called before triggering itemSelectionChanged signal?

PYPL
  • 1,819
  • 1
  • 22
  • 45
  • It isn't. You start the thread, which immediately emits the `itemSelectionChanged` signal, which then calls `fill_List2`. What did you expect to happen? – ekhumoro May 19 '15 at 16:56
  • @ekhumoro i thought thread should start when i click on an item from List1...how do i have to fix the code to make it work as expected? – PYPL May 19 '15 at 19:16
  • 1
    You need to connect the `itemSelectionChanged` of `List1` to a slot which creates the thread, and starts it. Once the thread has finished, it should emit a custom signal which sends back the items for `List2` (you must not attempt to directly update the gui in the worker thread). – ekhumoro May 19 '15 at 21:47
  • What is `from ui import Ui_Win`? I'm not familiar with that import and therefore it makes question extremely hard to answer. You also haven't shown the code to set up or add the list widgets. I will write up an answer, but the question would be more useful for future users if you could show what `ui` is and also add the setup code. – J Richard Snape May 20 '15 at 14:10

2 Answers2

3
from ui import Ui_Win ## ui.py is a file that has been generated from Qt Designer and it contains main GUI objects like QListWidget
from PyQt4 import QtGui, QtCore

class GenericThread(QtCore.QThread):
    def __init__(self, parent=None, listIndex=0):
        QtCore.QThread.__init__(self, parent)
        self.listIndex = listIndex

    def __del__(self):
        self.quit()
        self.wait()

    def run(self):
        if self.listIndex == 2:
            for addStr in Something:
                #Some long stuff
                self.emit( QtCore.SIGNAL('fillListWithItems(QString, int'), addStr, self.listIndex)

class MainUI(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self)
        self.ui = Ui_Win()
        self.ui.setupUi(self)
        ...

        self.ui.List1.list1SelectedItem.connect(self.fill_List2)

    @QtCore.pyqtSlot(QString, int)
    def self.fillListWithItems(addStr, lstIdx):
        if lstIdx==2:
            self.ui.List2.addItem(addStr)

    def fill_List2(self):
        self.ui.List2.clear()
        list1SelectedItem = str(self.ui.List1.currentItem().text())

        genericThread = GenericThread(self, listIndex=2)
        self.connect(genericThread, QtCore.SIGNAL("fillListWithItems(QString, int)"), self.fillListWithItems )
        genericThread.start()

Thanks @ekhumoro

PYPL
  • 1,819
  • 1
  • 22
  • 45
  • Ah, I see you've got a working version for yourself. I came to this via the comment you left in Python chat a while ago - don't know why it didn't show me that you'd already got the answer. Anyhow - I'll repeat my comment here - I think it would be helpful for any future visitors if you outlined what `ui` was and showed how `List1` and `List2` were populated – J Richard Snape May 20 '15 at 14:18
  • And your solution with the custom signal is better - I'm not sure mine is very good even though it works, so I think I'll delete it or edit it. – J Richard Snape May 20 '15 at 14:20
  • @JRichardSnape And the `Ui_Win` is nothing but generated code from QtDesigner, it doesn't make any major sense – PYPL May 20 '15 at 14:51
  • OK - I'll undelete it, I guess it makes an alternative and maybe someone will comment if they don't like it emitting an itemSelectionChanged. Understood on the `UI_Win` front - maybe just put a line in stating that's what it is and that it doesn't contain anything very useful, but does hold the `List1` and `List2` widgets and they are `QListWidget` type (I assume they are) – J Richard Snape May 20 '15 at 14:57
0

Your problem is that simply starting the thread is firing the itemSelectionChanged() signal (because it is in the run function), which you have connected to your fill_List2() function. You need to connect the itemSelectionChanged event on List1 to a SLOT that will trigger the thread to do the heavy computation and update List2.

I've had to make a number of assumptions about what ui is and what List1 and List2 are and how they're set up, but here is a working example. I've replaced the heavy computation in your GenericThread with a simple 2 second delay.

If I've misinterpreted / made bad assumptions, please update the question and leave a comment

test_slotting.py

from ui import Ui_Win
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSlot
from PyQt4.QtGui import *
import time

class GenericThread(QtCore.QThread):
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

    def __del__(self):
        self.quit()
        self.wait()

    def run(self):
        #Do all your heavy processing here
        #I'll just wait for 2 seconds
        time.sleep(2)
        self.emit( QtCore.SIGNAL('itemSelectionChanged()'))
        return

class MainUI(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self)
        self.ui = Ui_Win()
        self.ui.setupUi(self)
        self.ui.List1 = QListWidget(self)
        self.ui.List2 = QListWidget(self)

        hbox = QtGui.QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(self.ui.List1)
        hbox.addWidget(self.ui.List2)

        self.ui.centralWidget.setLayout(hbox)

        self.ui.List1.addItems(['alpha','beta','gamma','delta','epsilon'])
        self.ui.List2.addItems(['Item1','Item2'])

        self.ui.List1.itemSelectionChanged.connect(self.start_heavy_processing_thread)

    @pyqtSlot()
    def start_heavy_processing_thread(self):
        genericThread = GenericThread(self)
        self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
        genericThread.start()

    def fill_List2(self):
        self.ui.List2.clear()
        list1SelectedItem = str(self.ui.List1.currentItem().text())
        self.ui.List2.addItem(list1SelectedItem)

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindow = MainUI()
    MainWindow.show()
    sys.exit(app.exec_())

ui.py

from PyQt4 import QtCore, QtGui

class Ui_Win(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(416, 292)
        self.centralWidget = QtGui.QWidget(MainWindow)
        self.centralWidget.setObjectName("centralWidget")
        MainWindow.setCentralWidget(self.centralWidget)

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindow = QtGui.QMainWindow()
    ui = Ui_Win()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())
J Richard Snape
  • 20,116
  • 5
  • 51
  • 79