1

Basically, what I have is a GUI with some QLineEdits, a "search button" and a table. You hit the button and a class called DataGrabber searches a database for data, processes them, returns a list with dictionaries with which the table is filled, accordingly. These searches can take a while and I need to keep my GUI responsive. Also, I want the statusbar message to change as long as the search is going on (something like "Searching." -> "Searching.." -> "Searching...", the functionality is not rly important here, it's just about understanding how I can handle this properly).

I started with threading everything and created a queue between the thread that handles the search and the function that handles the statusbar, to know when the search is done. But that seems really goofy. Especially since Qt provides all kind of tools, like QThread and Signals. But I'm rly lost right now. What would be the best way to handle the responsiveness when having such a time-consuming action like a database search? And what would be the best way to tell the main/child thread that the search is finished?

Here is a reduced version of what I have right now:

class GUI(Ui_MainWindow, InitGlobals):
    def __init__(dialog):
        ...
        self.start_button_3.clicked.connect(\
                 lambda: self.start_search(self.result_tab_3))
        ...
    def start_search():
       ...
       search_paras = [3,
                       self.name_box_3.text(),
                       self.project_combo_3.currentText(),
                       self.voltage_box.text(),
                       self.volume_box.text()]
       queue = Queue()
       thr = Thread(target=self.search_thread, args=(queue, search_paras,))
       thr.start()
       data_lst = statusbar_load(queue, self.statusbar, option="loader")
       thr.join()
       self.statusbar.showMessage("Search completed...")

       for dic in data_lst:
            self.write_to_table(dic, tab)

    def search_thread(self, queue, args):
        grabber = DataGrabber(self.db_config)
        ...
        if args[0] == 3:
            queue.put(grabber.alpha_search(args[1], args[2],
                                           args[3], args[4]))
        queue.task_done()

    def statusbar_load(queue, statusbar_obj, option="spinner"):
        data = None
        i = 0
        while data is None:
            try:
                data = queue.get(timeout=0.1)
            except Empty:
                if option == "spinner":
                    status = ["-", "\\", "|", "/"]
                    statusbar_obj.showMessage("Searching  [" + status[i%4] + "]")
                ....
                i = i + 1
        return data
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
dieggo111
  • 81
  • 10

1 Answers1

1

This can be handled with signals. You can use signals to send the results to the GUI and to update the GUI of the progress.

Here is a quick example of the implementation with a progress bar and status label. These can be included in a status bar if you would like:

class GUITest(QtWidgets.QWidget):

    def __init__(self):
        QtWidgets.QWidget.__init__(self)

        layout = QtWidgets.QGridLayout()

        self.button = QtWidgets.QPushButton('Run')
        self.button.clicked.connect(self.run)

        self.result_box = QtWidgets.QTextBrowser()

        self.label = QtWidgets.QLabel()

        self.progress_bar = QtWidgets.QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)

        layout.addWidget(self.button)
        layout.addWidget(self.result_box)
        layout.addWidget(self.label)
        layout.addWidget(self.progress_bar)

        self.setLayout(layout)

    def run(self):
        self.progress_bar.setVisible(True)
        self.label.setText('Searching...')
        self.thread = QtCore.QThread()
        self.data_grabber = DataGrabber()
        self.data_grabber.moveToThread(self.thread)
        self.data_grabber.update_progress.connect(self.update_progress_bar)
        self.data_grabber.results.connect(self.display_results)
        self.data_grabber.finished.connect(self.complete)
        self.data_grabber.finished.connect(self.thread.quit)
        self.data_grabber.finished.connect(self.data_grabber.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.started.connect(self.data_grabber.run)
        self.thread.start()

    def update_progress_bar(self):
        self.progress_bar.setValue(self.progress_bar.value() + 1)

    def complete(self):
        self.label.setText('Complete')
        self.progress_bar.setVisible(False)

    def display_results(self, results):
        for key, value in results.items():
            self.result_box.append('%s: %s' % (key, value))


class DataGrabber(QtCore.QObject):

    finished = QtCore.pyqtSignal()
    update_progress = QtCore.pyqtSignal()
    results = QtCore.pyqtSignal(dict)  # set the type of object you are sending

    def __init__(self):
        super().__init__()
        self.count = 0

    def run(self):
        # search database here and emit update_progress when appropriate
        while self.count <= 100:
            self.update_progress.emit()
            self.count += 1
            time.sleep(0.02)
        self.send_results()  # when done, send the results
        self.finished.emit()

    def send_results(self):
        results = {'one': 'Result One', 'two': 'Result Two', 'three': 'Result Three'}
        self.results.emit(results)
MalloyDelacroix
  • 2,193
  • 1
  • 13
  • 17
  • One thing, though: Let's say I insert my search function atop the while loop inside the run() function (something like data = search_DB()). Then the while loop and the progressbar action will not start before the search is finished (which may take several seconds). This needs to happen in parallel. Must this be realized with another signal and class statusbar? – dieggo111 Jun 06 '18 at 11:54
  • Depending on how exactly it's executed, a database search is probably not a good place for a progress bar. To update the progress bar you need to have incremental progress that you can monitor. I'm not sure how you would get incremental updates from a database search. Probably the best bet would be set the label to indicate the search has started before calling `data = search_DB()` and change the label to indicate the search is complete by connecting to the finished signal. The progress bar would be more useful if the data processing stage, after the data is retrieved, took some time. – MalloyDelacroix Jun 06 '18 at 12:08