Integrating PyQt5 GUI with other python program - python

I have build a GUI with PyQt5 and I now want to integrate it with another python program. I have previously done this by placing GUI code written in TKinter in a separate thread. I want to know how to integrate this PyQt GUI into my other code the best way.
My other program has 2 threads monitoring rotary encoders and the main thread works on a RFID reader + some database communication.
Do the PyQt GUI have to be the main thread of the program?
Here is the PyQt gui:
from PyQt5 import QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QLabel
from PyQt5.QtGui import QPixmap
import sys
class Reminder():
def __init__(self):
self.activity = 'activity' # instance variable unique to each instance
self.time = 0000
self.day = 'day'
self.multiple_days = False
class Window(QWidget):
def __init__(self):
super().__init__()
self.titel = "GUI for Andreas"
self.top = 150
self.left = 150
self.width = 500
self.height = 500
self.setWindowIcon(QtGui.QIcon("calender.png"))
self.InitWindow()
def InitWindow(self):
self.setWindowTitle(self.titel)
self.setGeometry(self.top,self.left,self.width,self.height)
self.creatingTables(test_list)
self.vBoxLayout = QVBoxLayout()
self.vBoxLayout.addWidget(self.tableWidget)
self.setLayout(self.vBoxLayout)
self.textWidgets()
self.vBoxLayout.addWidget(self.timelabel)
self.vBoxLayout.addWidget(self.daylabel)
self.show()
def creatingTables(self, planned_activities):
self.tableWidget = QTableWidget()
self.tableWidget.setRowCount(len(planned_activities))
self.tableWidget.setColumnCount(4)
count = 0
for i in planned_activities:
index = 0 + count
self.label = QLabel(self)
self.label.setPixmap(QPixmap('%s.png' %i.activity))
self.tableWidget.setCellWidget(index,0, self.label)
self.tableWidget.setItem(index,1, QTableWidgetItem(i.activity))
self.tableWidget.setItem(index,2, QTableWidgetItem(i.day))
self.tableWidget.setItem(index,3, QTableWidgetItem(str(i.time)))
count +=1
def textWidgets(self):
self.timelabel = QLabel(self)
self.daylabel = QLabel(self)
self.timelabel.setText("Time:%s" %glo_dict['Time'])
self.daylabel.setText("Day:%s" %glo_dict['Day'])
glo_dict = {'Time': 0000, 'Day': 'None'} #This dict will be updated by other threads in main code
#Simple test object, these objects will be made by the main thread in the other program.
test_list=[]
a = Reminder()
a.time = 1123
a.day = 'Monday'
a.activity = 'rolling'
test_list.append(a)
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())

Related

PyQt GUI crashing while typing in LineEdit without any traceback error [duplicate]

This question already has answers here:
In PyQt, what is the best way to share data between the main window and a thread
(1 answer)
Background thread with QThread in PyQt
(7 answers)
Example of the right way to use QThread in PyQt?
(3 answers)
Closed 4 months ago.
I'm trying to make Typing speed test app in Pyqt, but it recently started crashing when I was inside QLineEdit. Sometimes it crashed instantly after I tried typing, sometimes only after tens of character were typed.
My code:
from PyQt5.QtWidgets import QWidget, QMainWindow, QApplication, QStackedWidget, QPushButton, QSizePolicy, QLabel, QLineEdit, QHBoxLayout, QVBoxLayout
from PyQt5.QtCore import *
import sys
import time
import random
import threading
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle("Typing Speed Test App")
self.setMinimumSize(1280,720)
global stacked
stacked = QStackedWidget(self)
self.setCentralWidget(stacked)
stacked.addWidget(Menu())
stacked.addWidget(TS_Test())
stacked.addWidget(Statistics())
stacked.setCurrentIndex(1) # test only
class Menu(QWidget):
def __init__(self):
QWidget.__init__(self)
self.frameWidth = self.frameSize().width()
self.frameHeight = self.frameSize().height()
self.initUI()
def initUI(self):
self.button_test = QPushButton(self)
self.button_graph = QPushButton(self)
self.button_test.setFixedWidth(50)
self.button_test.clicked.connect(lambda: stacked.setCurrentIndex(1))
self.button_graph.clicked.connect(lambda: stacked.setCurrentIndex(2))
self.button_graph.clicked.connect(lambda: print(self.frameSize().width()))
self.button_test.move(self.frameSize().width()*50//100-50,200)
self.button_graph.move(200,230)
class TS_Test(QWidget):
def __init__(self):
QWidget.__init__(self)
f = open('paragraphs.txt').read()
self.sentences = f.split('BREAK\n')
self.sentence = random.choice(self.sentences)
self.sentence = self.sentence.strip('\n')
self.word = self.sentence.split()
self.setStyleSheet("QLabel{font-size: 15px;}")
self.initUI()
self.start_thread()
def initUI(self):
self.button_back = QPushButton(self)
self.button_back.clicked.connect(lambda: stacked.setCurrentIndex(0))
self.button_back.move(30,50)
self.lineEdit = QLineEdit()
self.label = QLabel()
self.accuracy_label = QLabel()
self.wpm_label = QLabel()
self.first_letter = self.sentence[0]
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.label)
self.layout.addWidget(self.lineEdit)
self.layout.addWidget(self.accuracy_label)
self.layout.addWidget(self.wpm_label)
self.label.setText(self.sentence)
self.layout.setContentsMargins(250,250,250,300)
self.setLayout(self.layout)
def start_thread(self):
self.t_start=threading.Thread(target=self.start)
self.t_start.start()
def start(self):
while True:
if len(self.lineEdit.text()) > 0:
if self.lineEdit.text()[0] == self.first_letter:
self.time_thread()
break
def time_thread(self):
print('start')
timer_start = time.perf_counter()
self.correct_char = 0
while True:
if (len(self.lineEdit.text()) == len(self.sentence)) and (self.lineEdit.text().split()[-1] == self.word[-1]):
self.written_word = self.lineEdit.text().split(' ')
timer_stop = time.perf_counter()
timer = timer_stop - timer_start
self.wpm = len(self.written_word) / timer * 60
for i in range(len(self.sentence)):
if self.lineEdit.text()[i] == self.sentence[i]:
self.correct_char += 1
self.accuracy = self.correct_char / len(self.sentence) * 100
print(f"Accuracy = {self.correct_char / len(self.sentence) * 100}")
print(f'WPM: {self.wpm:0.3f}')
self.accuracy_label.setText(f'Accuracy = {self.accuracy}%')
self.wpm_label.setText(f'WPM: {self.wpm:0.3f}')
break
class Statistics(QWidget):
def __init__(self):
QWidget.__init__(self)
self.initUI()
def initUI(self):
self.button_back = QPushButton(self)
self.button_back.clicked.connect(lambda: stacked.setCurrentIndex(0))
self.button_back.move(400,300)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainwin = MainWindow()
mainwin.show()
sys.exit(app.exec_())
I've tried doing try and except so that it can print me traceback error, but even that didn't print anything. I read somewhere that it might be because I'm using library threading instead of QThread, but I have no idea if that has something to do with it.
EDIT:
Content in paragraphs.txt:
This line serves as test.
BREAK
Sentence number one.
BREAK
Sentence number two.
BREAK
Line number three.

I created a class for the QPushButton widget in PyQt5, but it doesn't show after I create an instance of it

I wanted to try and see if I could create a class for the QPushButton widget in PyQt5 to save some lines. But once I create to create an instance of the Button class, it doesn't appear anywhere on the win instance of the Window class.
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Nested Layouts")
self.resize(800, 200)
self.show()
class Button(QPushButton):
def __init__(self, btn_parent, btn_text, btn_width, btn_height, btn_xCor, btn_yCor):
super().__init__()
self.btn_parent = btn_parent
self.btn_text = btn_text
self.btn_width = btn_width
self.btn_height = btn_height
self.btn_xCor = btn_xCor
self.btn_yCor = btn_yCor
self.setText(self.btn_text)
self.resize(btn_width, btn_height)
self.move(btn_xCor, btn_yCor)
app = QApplication(sys.argv)
win = Window()
button = Window.Button(btn_parent=win, btn_text="This won't work", btn_width=10, btn_height=10, btn_xCor=10, btn_yCor=10)
app.exec()
I even tried to make it like this, but the results are the same:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Nested Layouts")
self.resize(800, 200)
self.show()
class Button(QPushButton):
def __init__(self, btn_parent, btn_text, btn_width, btn_height, btn_xCor, btn_yCor):
super().__init__()
self.btn_parent = btn_parent
self.btn_text = btn_text
self.btn_width = btn_width
self.btn_height = btn_height
self.btn_xCor = btn_xCor
self.btn_yCor = btn_yCor
self.setText(self.btn_text)
self.resize(btn_width, btn_height)
self.move(btn_xCor, btn_yCor)
app = QApplication(sys.argv)
win = Window()
button = Button(btn_parent=win, btn_text="This won't work", btn_width=10, btn_height=10, btn_xCor=10, btn_yCor=10)
app.exec()
Keep in mind that I'm kind of new to OOP(and Python and programming in general), so I might have a misunderstanding about how OOP works.
How can I fix this code so that the button shows on the win instance?

Opening new window while out of focus using "keyboard"

I am trying to track my keypresses, using the module "keyboard" while a PySide2 Widget is not in focus, which works fine. However when I try to create a new Widget using a "keyboard" shortcut the program crashes. Opening a window on a button press works fine. I can also call non UI functions using "keyboard" eg. the print function without any problem.
Do you know a way to fix this and open a new window using "keyboard" or any other method, while a PySide2 window is not in focus. In this example I want to open a new window on "CTRL+D". The Problem exists both for PySide2 and PyQt5.
This is my shortened code:
import sys
import json
import os
import keyboard
from PySide2.QtWidgets import QApplication, QWidget, QGridLayout, QKeySequenceEdit, QLabel, QPushButton, QShortcut
from PySide2.QtCore import Qt, QObject, Signal, Slot # Qt.Key_W beispielsweise
#from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QKeySequenceEdit, QLabel, QPushButton, QShortcut
#from PyQt5.QtCore import Qt, QObject, pyqtSignal as Signal, pyqtSlot as Slot # Qt.Key_W beispielsweise
class ConfigWindow(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.init_shortcuts()
self.show()
def initUi(self):
self.setGeometry(300,300, 400, 250)
self.setWindowTitle("Settings")
grid = QGridLayout()
self.setLayout(grid)
self.keyseq = QKeySequenceEdit("CTRL+D")
grid.addWidget(self.keyseq, 0, 0)
s_button = QPushButton("Safe")
grid.addWidget(s_button, 1, 0)
cl_button = QPushButton("Close")
grid.addWidget(cl_button, 1, 1)
cl_button.clicked.connect(self.close)
open_button = QPushButton("openw")
grid.addWidget(open_button, 2, 0)
open_button.clicked.connect(self.call_item_parser)
def keyPressEvent(self, event): #event:PySide2.QtGui.QKeyEvent
if event.key() == Qt.Key_Escape:
self.close()
# shortcuts are listened to, while program is running
def init_shortcuts(self):
str_value = self.keyseq.keySequence().toString()
print("Binding _price_keyseq to {}".format(str_value))
keyboard.add_hotkey(str_value, self.call_item_parser)
# keyboard.add_hotkey(str_value, print, args=("this works")) # this would work
def call_item_parser(self):
self.h_w = ParseWindow()
self.h_w.setWindowTitle("New Window")
self.h_w.setGeometry(100, 100, 100, 100)
self.h_w.show()
class ParseWindow(QWidget):
def __init__(self):
super().__init__()
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())
The problem is caused because the callback registered in keyboard is executed in a secondary thread as can be verified by modifying the following part of the code and printing threading.current_thread(). In Qt it is forbidden to create any widget in another thread since they are not thread-safe.
def call_item_parser(self):
print(threading.current_thread())
self.h_w = ParseWindow()
self.h_w.setWindowTitle("New Window")
self.h_w.setGeometry(100, 100, 100, 100)
self.h_w.show()
print(threading.current_thread())
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())
Output:
<_MainThread(MainThread, started 140144979916608)>
Binding _price_keyseq to ctrl+a
<Thread(Thread-10, started daemon 140144220817152)>
One possible solution is to use a signal to send the information to the main thread, and invoke the callback in the main thread.
import sys
from functools import partial
import platform
import threading
import keyboard
from PySide2.QtCore import Qt, QObject, Signal, Slot
from PySide2.QtGui import QKeySequence
from PySide2.QtWidgets import (
QApplication,
QWidget,
QGridLayout,
QKeySequenceEdit,
QPushButton,
)
class KeyBoardManager(QObject):
activated = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._callbacks = dict()
self.activated.connect(self._handle_activated)
#property
def callbacks(self):
return self._callbacks
def register(self, shortcut, callback, *, args=(), kwargs=None):
self.callbacks[shortcut] = (callback, args, kwargs or {})
keyboard.add_hotkey(shortcut, partial(self.activated.emit, shortcut))
#Slot(str)
def _handle_activated(self, shortcut):
values = self.callbacks.get(shortcut)
if values is not None:
callback, args, kwargs = self._callbacks[shortcut]
callback(*args, **kwargs)
class ConfigWindow(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.init_shortcuts()
self.show()
def initUi(self):
self.setGeometry(300, 300, 400, 250)
self.setWindowTitle("Settings")
grid = QGridLayout(self)
self.keyseq = QKeySequenceEdit("CTRL+A")
grid.addWidget(self.keyseq, 0, 0)
s_button = QPushButton("Safe")
grid.addWidget(s_button, 1, 0)
cl_button = QPushButton("Close")
grid.addWidget(cl_button, 1, 1)
cl_button.clicked.connect(self.close)
open_button = QPushButton("openw")
grid.addWidget(open_button, 2, 0)
open_button.clicked.connect(self.call_item_parser)
def keyPressEvent(self, event): # event:PySide2.QtGui.QKeyEvent
if event.key() == Qt.Key_Escape:
self.close()
# shortcuts are listened to, while program is running
def init_shortcuts(self):
self.keyboard_manager = KeyBoardManager()
str_value = self.keyseq.keySequence().toString()
if platform.system() == "Linux":
str_value = str_value.lower()
print("Binding _price_keyseq to {}".format(str_value))
self.keyboard_manager.register(str_value, self.call_item_parser)
def call_item_parser(self):
print(threading.current_thread())
self.h_w = ParseWindow()
self.h_w.setWindowTitle("New Window")
self.h_w.setGeometry(100, 100, 100, 100)
self.h_w.show()
class ParseWindow(QWidget):
pass
def main():
print(threading.current_thread())
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Output:
<_MainThread(MainThread, started 140037641176896)>
Binding _price_keyseq to ctrl+a
<_MainThread(MainThread, started 140037641176896)>

Python PyQt5 CreateTable class inheriting QtWidgets.QTableWidget to add add_row function

I am trying to create an application with a table in it to display rows of data. When more data arrives, I'd like to add a row and enter the data. However, I'm stuck at adding the row. Also, my own CreateTable class that inherits QtWidgets.QTableWidget works, but I don't understand why the line
self = QTableWidget(win)
is required. I've tried to keep the code as simple as possible just now until I get round having the table as an object with custom functions (add_row, delete_row...). Here is my code:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem
import sys
class CreateTable(QTableWidget):
def __init__(self, win):
super().__init__()
# Doesn't display table without the next line. I don't understand why it is required.
self = QTableWidget(win)
#
self.setGeometry(50, 50, 1820, 920)
self.setRowCount(10)
self.setColumnCount(10)
def add_row(self):
rowcount = self.rowCount()
self.insertRow(rowcount)
class MainWindow(QMainWindow):
def __init__(self):
"""MainWindow constructor."""
super().__init__()
# Main UI code
self.setGeometry(0, 0, 1920, 1040)
self.setWindowTitle("Order Manager")
table = CreateTable(self)
table.add_row()
# Main code
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
This code opens the window and displays a table with 10 rows, but doesn't add the row to make 11 rows. I am quite new to OOP and PyQt5, but am trying to learn more!
New working code:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem
import sys
class MainWindow(QMainWindow):
def __init__(self):
"""MainWindow constructor."""
super().__init__()
# Main UI code
self.setGeometry(0, 0, 1920, 1040)
self.setWindowTitle("Order Manager")
self.table = CreateTable(self)
self.table.add_row()
class CreateTable(QTableWidget):
def __init__(self, win):
super().__init__(win)
self.setGeometry(50, 55, 1820, 920)
self.setRowCount(10)
self.setColumnCount(10)
def add_row(self):
rowcount = self.rowCount()
self.insertRow(rowcount)
# Main code
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())

Calculate a value and update this in a GUI python, without block my gui

I have a simple application, one label and 2 buttons ( start increment, stop increment), write in pyqt5.
When I pres start button want to see in ui, value update in real time and have acces at stop button.
Now when I press start button, not see an update in UI and when try to press on stop receive not responding.
One solution probably is Threads, but I don't understand how work thread's in Python
import sys
import time
from PyQt5 import QtWidgets, Qt
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QLineEdit
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
running = True
value = 1
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(600, 500))
self.setMaximumSize(QSize(600, 500))
self.setWindowTitle("Demo app")
QApplication.setStyle("fusion")
self.move(1000, 200)
self.button_start = QPushButton('Start', self)
self.button_start.setFixedHeight(40)
self.button_start.setFixedWidth(170)
self.button_start.move(10, 215)
self.button_start.clicked.connect(self.start_function)
self.update()
self.button_stop = QPushButton('Stop', self)
self.button_stop.setFixedHeight(40)
self.button_stop.setFixedWidth(170)
self.button_stop.move(200, 215)
self.button_stop.setDisabled(True)
self.button_stop.clicked.connect(self.stop_function)
self.update()
self.label = QLineEdit(self)
self.label.move(10, 170)
self.label.resize(170, 40)
self.label.setEnabled(False)
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("color: red;")
self.update()
def start_function(self):
self.button_start.setDisabled(True)
self.button_stop.setDisabled(False)
while self.running is True:
self.value += 1
self.label.setText(str(self.value))
print("Value: ", self.value)
time.sleep(1)
def stop_function(self):
self.running = False
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
mainWin.update()
sys.exit(app.exec_())
Maybe I'm a little late and you figured out yourself how to solve this problem, but for those with the same issue:
You can't set a loop in the GUI thread for this purpose, It will block the GUI thread from doing anything. Instead, You can use QTimer to schedule something to run at a later point in time. You can use it to implement something like a stopwatch.
Here is a functional minimal example:
import sys
import time
from PyQt5 import QtWidgets, Qt
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QLineEdit
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
value = 0
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(600, 500))
self.setMaximumSize(QSize(600, 500))
self.setWindowTitle("Demo app")
QApplication.setStyle("fusion")
self.move(1000, 200)
self.button_start = QPushButton('Start', self)
self.button_start.setFixedHeight(40)
self.button_start.setFixedWidth(170)
self.button_start.move(10, 215)
self.button_start.clicked.connect(self.start_function)
self.update()
self.button_stop = QPushButton('Stop', self)
self.button_stop.setFixedHeight(40)
self.button_stop.setFixedWidth(170)
self.button_stop.move(200, 215)
self.button_stop.setDisabled(True)
self.button_stop.clicked.connect(self.stop_function)
self.update()
self.label = QLineEdit(self)
self.label.move(10, 170)
self.label.resize(170, 40)
self.label.setEnabled(False)
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("color: red;")
self.label.setText(str(self.value))
self.update()
self.workTimer = QTimer()
self.workTimer.setInterval(1000)
self.workTimer.timeout.connect(self.increase_value)
def start_function(self):
self.workTimer.start()
self.button_start.setDisabled(True)
self.button_stop.setDisabled(False)
def stop_function(self):
self.workTimer.stop()
self.button_start.setDisabled(False)
self.button_stop.setDisabled(True)
def increase_value(self):
self.value += 1
self.label.setText(str(self.value))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
mainWin.update()
sys.exit(app.exec_())

Categories

Resources