Многопоточность в GUI на PyQt5 Python
Я написал небольшое приложение на PyQt5. На главном окне есть таблица, которая при нажатии на кнопку "Считать" начинает заполняться данными (около 12 тысяч строк) из базы данных SQLite.
Одновременно с заполнением таблицы мне нужно чтобы появлялось окно с прогрессбаром и таймерами прошедшего времени.
Так же в левом нижнем углу окна есть индикаторы Tx и Rx - они должны мигать, когда происходит чтение/запись данных.
Собственно, сам вопрос, как максимально просто реализовать многопоточность такой программы?
Нужно ли каким-то образом запустить каждый процесс (запись данных, таймеры, индикаторы) в отдельном потоке?
Как тогда передавать данные из одного потока в другой?
Как, например, индикатор, должен узнать, что в другом потоке произошла запись в таблицу и мигнуть?
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# конфигурация главного окна и центрального виджета
self.central_widget = QWidget(self)
self.setGeometry(50, 50, 1500, 800)
self.setWindowTitle('Чтение БКУ')
self.setCentralWidget(self.central_widget)
self.layout_main_window = QVBoxLayout()
self.central_widget.setLayout(self.layout_main_window)
# конфигурация верхней панели инструментов
self.btn_start_reading = QPushButton("Считать")
self.btn_start_reading.clicked.connect(self.start_read)
self.btn_download_from_file = QPushButton("Загрузить из файла")
self.btn_download_from_file.clicked.connect(self.download_from_file)
self.btn_save_to_file = QPushButton("Экспорт")
self.btn_save_to_file.clicked.connect(self.save_to_file)
self.layout_toolbar = QHBoxLayout()
self.layout_toolbar.addStretch(1)
self.layout_toolbar.addWidget(self.btn_start_reading)
self.layout_toolbar.addWidget(self.btn_download_from_file)
self.layout_toolbar.addWidget(self.btn_save_to_file)
self.layout_main_window.addLayout(self.layout_toolbar)
# конфигурация таблицы
self.layout_table = QHBoxLayout()
self.table = QTableWidget()
self.table.setColumnCount(9)
self.table.setRowCount(13500)
self.table.setHorizontalHeaderLabels(['№', 'Дата и время', 'БКУ', 'КЛ', 'АУ', 'Канал', 'Код события', 'Доп. параметр', 'Описание'])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setVisible(True)
self.layout_table.addWidget(self.table)
self.layout_main_window.addLayout(self.layout_table)
# конфигурация статус панели
self.layout_status_bar = QHBoxLayout()
self.layout_status_bar.addStretch(0)
self.layout_status_bar.setDirection(QBoxLayout.RightToLeft)
self.btn_select_port = QPushButton('Выбрать порт')
self.btn_select_port.clicked.connect(self.select_port)
self.label_current_port = QLabel()
if combobox_remembered_text() in ports_list:
self.label_current_port.setText(f': {combobox_remembered_text()}')
else:
self.select_port.setCurrentIndex(ports_list.index('Не выбран'))
# конфигурация Rx Tx:
self.label_tx = QLabel('Tx: ')
self.rb_tx = QRadioButton()
self.rb_tx.setObjectName('rb_tx')
self.label_rx = QLabel('Rx: ')
self.rb_rx = QRadioButton()
self.rb_rx.setObjectName('rb_rx')
self.setStyleSheet(rx_tx_stylesheet)
# __________________________________
self.label_trans_packets = QLabel('Передано: 0 ')
self.label_rec_packets = QLabel('Принято: 0 ')
self.label_count_of_log_entries = QLabel('Количество записей: 0 ')
self.label_session_time = QLabel('00:00:00')
self.layout_status_bar.addWidget(self.label_session_time)
self.layout_status_bar.addWidget(self.label_count_of_log_entries)
self.layout_status_bar.addWidget(self.label_rec_packets)
self.layout_status_bar.addWidget(self.label_trans_packets)
self.layout_status_bar.addWidget(self.rb_rx)
self.layout_status_bar.addWidget(self.label_rx)
self.layout_status_bar.addWidget(self.rb_tx)
self.layout_status_bar.addWidget(self.label_tx)
self.layout_status_bar.addSpacing(30)
self.layout_status_bar.addWidget(self.label_current_port)
self.layout_status_bar.addWidget(self.btn_select_port)
self.layout_main_window.addLayout(self.layout_status_bar)
# Создание потока для мигающих индикаторов
self.thread = ReadingRxTxThread()
self.thread.dateSignal.connect(self.update_data)
def start_read(self):
# запуск потока для
self.thread.start()
# Открытие окна с прогрессбаром для отслеживания процесса записи данных
modal_dialog = ReadingProgressDialog()
modal_dialog.show()
modal_dialog.exec_()
self.table.clearContents()
i = 0
# запись данных в таблицу
for event in session.query(Event).all():
self.table.setItem(i, 0, QTableWidgetItem(str(i+1)))
self.table.item(i, 0).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 1, QTableWidgetItem(str(event.date_time)))
self.table.item(i, 1).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 2, QTableWidgetItem(str(event.bku)))
self.table.item(i, 2).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 3, QTableWidgetItem(str(event.kl)))
self.table.item(i, 3).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 4, QTableWidgetItem(str(event.au)))
self.table.item(i, 4).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 5, QTableWidgetItem(str(event.channel)))
self.table.item(i, 5).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 6, QTableWidgetItem(str(event.event_code)))
self.table.item(i, 6).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 7, QTableWidgetItem(str(event.addit_param)))
self.table.item(i, 7).setBackground(QColor(codes_dictionary[event.event_code][1]))
self.table.setItem(i, 8, QTableWidgetItem(str(event.description)))
self.table.item(i, 8).setBackground(QColor(codes_dictionary[event.event_code][1]))
i += 1
def save_to_file(self):
pass
def download_from_file(self):
pass
def select_port(self):
modal_dialog = SelectPortDialog()
modal_dialog.show()
modal_dialog.exec_()
selected_port = modal_dialog.selected_port
self.label_current_port.setText(f': {selected_port}')
def update_data (self, text):
if text == 'Tx':
self.rb_tx.click()
elif text == 'Rx':
self.rb_rx.click()
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
При таком коде, при нажатии на кнопку "Считать" открывается модальное окно, но запись данных в таблицу не начинается, пока окно не закроется. А запись и отображение данных в окне должны происходить одновременно. Так же мне подсказали решение проблемы с мигающими индикаторами (Tx и Rx слева внизу таблицы), но как мне из потока с индикаторами получать информацию о записи данных в таблицу? Ниже код потока для индикаторов.
class ReadingRxTxThread(QtCore.QThread):
dateSignal = QtCore.pyqtSignal(str)
def __init__(self):
super().__init__()
def run(self):
# тут вы получаете какие-то данные с прибора
text = random.choice(['Tx', 'Rx'])
# тут вы испускаете сигнал и передаете какие-то данные
self.dateSignal.emit(text)
self.msleep(50)