我正在尝试使用包含多线程的PyQt5构建Python GUI。使用各种代码片段(主要从Stack Overflow获取),我提出了下面的代码,它将csv文件中的数据读入pandas数据帧并更新GUI(从QTimer触发)。
我想实现第二个线程,该线程从不同的csv文件和一个接受两个数据帧的函数读取,并在更新GUI之前对它们进行一些计算。我不确定如何在PyQt5的信号/插槽框架内做到这一点。任何帮助深表感谢!
import time
import traceback
import sys
import pandas as pd
import numpy as np
from PyQt5.QtWidgets import (QWidget, QTabWidget, QGridLayout, QApplication, QMainWindow, QStatusBar, QTableView)
from PyQt5.QtCore import (Qt, QTimer, QAbstractTableModel, QThread, QVariant, QObject, QRect, pyqtSlot, pyqtSignal)
class PandasModel(QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QAbstractTableModel.__init__(self, parent)
self._data = np.array(data.values)
self._cols = data.columns
self.r, self.c = np.shape(self._data)
def rowCount(self, parent=None):
return self.r
def columnCount(self, parent=None):
return self.c
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return self._data[index.row(), index.column()]
return None
def headerData(self, p_int, orientation, role):
if role == Qt.DisplayRole:
try:
if orientation == Qt.Horizontal:
return self._cols[p_int]
elif orientation == Qt.Vertical:
return p_int
except(IndexError, ):
return QVariant()
else:
return QVariant()
class WorkerSignals(QObject):
"""
Defines the signals available from a running worker thread.
Supported signals are:
finished: No data
error: `tuple` (exctype, value, traceback.format_exc() )
result: `object` data returned from processing, anything
progress: `int` indicating % progress
"""
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class Worker(QObject):
"""
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
:param callback: The function callback to run on this worker thread. Supplied args and
kwargs will be passed through to the runner.
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
"""
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
# kwargs['progress_callback'] = self.signals.progress
@pyqtSlot()
def run(self):
"""
Initialise the runner function with passed args, kwargs.
"""
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tab screen
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tabs.resize(300, 200)
# Add tabs
self.tabs.addTab(self.tab1, "Tab 1")
# Create tabs
self.tab1.layout = QGridLayout(self)
self.tab1.setLayout(self.tab1.layout)
# Create table widgets
self.table_widget1 = MyTableWidget(self)
# Add tables to tabs
self.tab1.layout.addWidget(self.table_widget1)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tables
self.table = QTableView()
# Add tabs to widget
self.layout.addWidget(self.table)
self.setLayout(self.layout)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Create a central Widgets
self.central_widget = QWidget()
# Create tab Widgets
self.tab_widget = MyTabWidget(self)
# Create a Layout for the central Widget
self.central_layout = QGridLayout()
# Set the Layout
self.central_widget.setLayout(self.central_layout)
# Set the Widget
self.setCentralWidget(self.central_widget)
self.central_layout.addWidget(self.tab_widget)
self.setGeometry(QRect(0, 100, 2000, 1500))
self.setWindowTitle('CSV viewer')
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.proxy = None
self.model = None
self.show()
# Setup worker thread
self.worker_thread = QThread(self)
self.worker = Worker(self.read_csv)
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.run)
# Connect worker signals to appropriate ports
self.worker.signals.result.connect(self.process_csv)
self.worker.signals.finished.connect(self.worker_thread.quit)
self.worker.signals.finished.connect(self.print_refresh_time)
# Setup timer to repetitively trigger start of worker thread e.g. every 5000ms
self.timer = QTimer()
self.timer.timeout.connect(self.worker_thread.start)
self.timer.start(5000)
def process_csv(self, df):
self.model = PandasModel(df)
self.tab_widget.table_widget1.table.setModel(self.model)
self.tab_widget.table_widget1.table.resizeColumnsToContents()
def print_refresh_time(self):
self.status_bar.showMessage('Last updated: ' + time.strftime("%d/%m/%Y %H:%M:%S", time.localtime()))
@staticmethod
def read_csv():
df = pd.read_csv("C:\\Users\\Brian\\Desktop\\E0.csv")
return df
def main():
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
if __name__ == '__main__':
main()