How to test a PyQt5 QApplication using pytest and pytest-qt? - python

I am trying to test my QtApplication using pytest-qt and need some assistance with the basics.
Give a simple mwe such as this:
# mwe.py
import sys
from PyQt5.QtWidgets import (QWidget, QToolTip,
QPushButton, QApplication)
from PyQt5.QtGui import QFont
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.count = 0
def initUI(self):
QToolTip.setFont(QFont('SansSerif', 10))
self.setToolTip('This is a <b>QWidget</b> widget')
btn = QPushButton('Button', self)
btn.setToolTip('This is a <b>QPushButton</b> widget')
btn.resize(btn.sizeHint())
btn.move(50, 50)
btn.clicked.connect(self.pushedTheButton)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Tooltips')
self.show()
def pushedTheButton(self):
print("Button pressed")
self.count += 1
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec())
if __name__ == '__main__':
main()
How would I test that the counter has incremented when the button is pressed?
from mwe import Example
import pytest
import pytestqt
from pytestqt.qtbot import QtBot
import sys
from PyQt5.Qt import QApplication
def test_press_button(qtbot: QtBot):
app = QApplication(sys.argv)
ex = Example()
qtbot.add_widget(ex)
sys.exit(app.exec())
# how to press the button and verify that counter is incermented?
Note, I've been able to do this outside the main loop, by just instantiating the Example but for my integration testing I want to have the full application open and running.
Thanks in advance!

Related

What is the signal for user clicks the down arrow on the QComboBox?

I need to execute a method whenever the user clicks the down arrow on the combo box. I've tried the signals listed in the documentations but none of the worked.
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.combo = QComboBox(self)
self.combo.signal.connect(self.mymethod)
self.show()
def mymethod(self):
print('hello world')
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
There is no signal that is emitted when the down arrow is pressed but you can create override the mousePressEvent method and verify that this element was pressed:
import sys
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import (
QApplication,
QComboBox,
QStyle,
QStyleOptionComboBox,
QVBoxLayout,
QWidget,
)
class ComboBox(QComboBox):
arrowClicked = pyqtSignal()
def mousePressEvent(self, event):
super().mousePressEvent(event)
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
sc = self.style().hitTestComplexControl(
QStyle.CC_ComboBox, opt, event.pos(), self
)
if sc == QStyle.SC_ComboBoxArrow:
self.arrowClicked.emit()
class Window(QWidget):
def __init__(self):
super().__init__()
self.combo = ComboBox()
self.combo.arrowClicked.connect(self.mymethod)
lay = QVBoxLayout(self)
lay.addWidget(self.combo)
lay.setAlignment(Qt.AlignTop)
def mymethod(self):
print("hello world")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())

How to embed a cmd to pyqt5 application

I tried codes below, but it seemed not work as normal. I cannot use embedded cmd whatever I did. It looks like decorations. I mean I just want to use the cmd like the ordinary one. Here, I paste the code, and any advice would be greatly appreciated.
I develop this app on Python 3.7.3 conda environment, Window10.
import sys
import subprocess
import time
import win32gui
from PyQt5.QtCore import QProcess, Qt
from PyQt5.QtGui import QWindow, QIcon, QFont
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QMdiArea, QSplitter, QTextBrowser
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout
from win32com import client
from win32gui import GetWindowText, EnumWindows,SetForegroundWindow
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.p = QProcess()
self.layout = QVBoxLayout()
self.mdi = QMdiArea()
self.mainSplitter = QSplitter(Qt.Vertical)
self.setCentralWidget(self.mainSplitter)
self.mainSplitter.addWidget(QTextBrowser())
self.initUI()
def initUI(self):
self.runExe()
EnumWindows(self.set_cmd_to_foreground, None)
hwnd1 = win32gui.GetForegroundWindow()
#hwnd1 = win32gui.FindWindow(None, "C:\\Windows\\system32\\calc.exe")
print(hwnd1)
window = QWindow.fromWinId(hwnd1)
container_widge = self.createWindowContainer(window, self)
container_widge.setFocusPolicy(Qt.TabFocus)
container_widge.setFocus()
container_widge.setWindowTitle("ain")
container_widge.setFont(QFont("Times New Roman"))
container_widge.setGeometry(500, 500, 450, 400)
#container_widge.setFocusPolicy()
container_widge.activateWindow()
container_widge.acceptDrops()
container_widge.grabMouse()
container_widge.setMouseTracking(True)
self.mainSplitter.addWidget(container_widge)
self.showMaximized()
#self.setGeometry(200, 200, 700, 700)
#self.show()
def runExe(self):
shell.run("cmd.exe")
time.sleep(1)
def set_cmd_to_foreground(self, hwnd, extra):
"""sets first command prompt to forgeround"""
if "cmd.exe" in GetWindowText(hwnd):
print(hwnd)
SetForegroundWindow(hwnd)
return
def run_script(self, shell, scripts):
"""runs the py script"""
shell.SendKeys(scripts+"{ENTER}")
if __name__ == '__main__':
shell = client.Dispatch("WScript.Shell")
app = QApplication(sys.argv)
ex = Example()
#ex.run_script(shell, "python -m pip list")
#ex.show()
sys.exit(app.exec_())
you can try this I got it from this blog:
import sys
from PyQt5.QtCore import QProcess, QTextStream
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit,
QLineEdit, QVBoxLayout, QHBoxLayout, QWidget, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.process = QProcess(self)
self.output = QTextEdit(self)
self.input = QLineEdit(self)
self.run_command_button = QPushButton("Run Command", self)
layout = QVBoxLayout()
input_layout = QHBoxLayout()
input_layout.addWidget(self.input)
input_layout.addWidget(self.run_command_button)
layout.addLayout(input_layout)
layout.addWidget(self.output)
central_widget = QWidget(self)
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.process.readyReadStandardOutput.connect(self.read_output)
self.run_command_button.clicked.connect(self.run_command)
self.process.start("cmd.exe")
def read_output(self):
stream = QTextStream(self.process)
self.output.append(stream.readAll())
def run_command(self):
command = self.input.text() + "\n"
self.process.write(command.encode())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.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_())

Line Animation with QPropertyAnimation

Trying to animate a line growing from nothing to a (0,0) to (200, 200) line with PyQt5 and using QPropertyAnimation. I already read a lot of documentation about Qt and tried several samples, but I just cannot get this to work. This is the code I have now:
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPainterPath
from PyQt5.QtCore import QObject, QPointF, QPropertyAnimation, pyqtProperty
from PyQt5.QtCore import QLineF
import sys
class Sample(QWidget):
l1 = QLineF(QPointF(), QPointF())
def __init__(self):
super().__init__()
self.initView()
self.initAnimation()
def initView(self):
self.show()
def initAnimation(self):
self.anim = QPropertyAnimation(self.l1, b'geometry')
self.anim.setDuration(7000)
self.anim.setStartValue(QPointF(0, 0))
self.anim.setEndValue(QPointF(200, 200))
self.anim.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Sample()
sys.exit(app.exec_())
Not posting all previous attemps, each one fails with a different error. I got a fade out animation on a widget to work, and a picture following a path, but I can seem to make a simple line drawing work. I was hoping to achieve something like this:
Codepen example
Qt documentation is huge and it seems there are several ways to achieve this, painter and timer, animation, variant animation, but I am not very familiar with C++ and the translation to Python is not always easy. Also samples are not that easy to find.
Am I missing something obvious?
Thanks!
For the record, this is what I achieved so far but as soon as I un-comment the QPropertyAnimation creation, app does not launch. Anyway I was still far from the result in the accepted answer.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MyLine(QGraphicsLineItem, QObject):
def __init__(self):
super().__init__()
def _set_start(self, point):
self.setLine(point.x(), point.y(), self.line().p2().x(), self.line().p2().y())
def _set_end(self, point):
self.setLine(self.line().p1().x(), self.line().p1().y(), point.x(), point.y())
start = pyqtProperty(QPointF, fset=_set_start)
end = pyqtProperty(QPointF, fset=_set_end)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
self.button = QPushButton("Start", self)
self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hbox.addWidget(self.button)
hbox.addSpacing(40)
self.line = MyLine()
self.line.setLine(0, 0, 10, 10)
scene = QGraphicsScene()
scene.addItem(self.line)
view = QGraphicsView(scene)
hbox.addWidget(view)
self.anim = QPropertyAnimation(self.line, b"end") # crash without error here
# self.anim.setDuration(2500)
# self.anim.setLoopCount(1)
# self.anim.setStartValue(QPointF(10, 10))
# self.anim.setEndValue(QPointF(200, 200))
# self.button.clicked.connect(self.anim.start)
self.setGeometry(300, 300, 380, 250)
self.setWindowTitle('Color anim')
self.show()
if __name__ == "__main__":
app = QApplication([])
ex = Example()
ex.show()
app.exec_()
You have to use QGraphicsView, QGraphicsScene with QGraphicsLineItem as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
class LineAnimation(QtCore.QObject):
def __init__(self, parent=None):
super(LineAnimation, self).__init__(parent)
self.m_line = QtCore.QLineF()
self.m_item = QtWidgets.QGraphicsLineItem()
self.m_item.setLine(self.m_line)
self.m_item.setPen(
QtGui.QPen(
QtGui.QColor("salmon"),
10,
QtCore.Qt.SolidLine,
QtCore.Qt.SquareCap,
QtCore.Qt.RoundJoin,
)
)
self.m_animation = QtCore.QPropertyAnimation(
self,
b"p2",
parent=self,
startValue=QtCore.QPointF(0, 0),
endValue=QtCore.QPointF(200, 200),
duration=5 * 1000,
)
self.m_animation.start()
def p1(self):
return self.m_line.p1()
def setP1(self, p1):
self.m_line.setP1(p1)
self.m_item.setLine(self.m_line)
def p2(self):
return self.m_line.p2()
def setP2(self, p2):
self.m_line.setP2(p2)
self.m_item.setLine(self.m_line)
p1 = QtCore.pyqtProperty(QtCore.QPointF, fget=p1, fset=setP1)
p2 = QtCore.pyqtProperty(QtCore.QPointF, fget=p2, fset=setP2)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(
scene, alignment=QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
)
self.setCentralWidget(view)
line_animation = LineAnimation(self)
scene.addItem(line_animation.m_item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

PyQt5 button to run function and update LCD

I am getting started with creating GUI's in PyQt5 with Python 3. At the click of the button I want to run the "randomint" function and display the returned integer to the QLCDNumber named "lcd".
Here's my code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLCDNumber
from random import randint
class Window(QWidget):
def __init__(self):
super().__init__()
self.initui()
def initui(self):
lcd = QLCDNumber(self)
button = QPushButton('Generate', self)
button.resize(button.sizeHint())
layout = QVBoxLayout()
layout.addWidget(lcd)
layout.addWidget(button)
self.setLayout(layout)
button.clicked.connect(lcd.display(self.randomint()))
self.setGeometry(300, 500, 250, 150)
self.setWindowTitle('Rand Integer')
self.show()
def randomint(self):
random = randint(2, 99)
return random
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())
I am getting the output:
TypeError: argument 1 has unexpected type 'NoneType'
How can I get the LCD to display the output from function "randomint"?
The problem is that the button.clicked.connect expects the slot (Python callable object), but lcd.display returns None. So we need a simple function (slot) for button.clicked.connect which will display your newly generated value. This is working version:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLCDNumber
from random import randint
class Window(QWidget):
def __init__(self):
super().__init__()
self.initui()
def initui(self):
self.lcd = QLCDNumber(self)
button = QPushButton('Generate', self)
button.resize(button.sizeHint())
layout = QVBoxLayout()
layout.addWidget(self.lcd)
layout.addWidget(button)
self.setLayout(layout)
button.clicked.connect(self.handleButton)
self.setGeometry(300, 500, 250, 150)
self.setWindowTitle('Rand Integer')
self.show()
def handleButton(self):
self.lcd.display(self.randomint())
def randomint(self):
random = randint(2, 99)
return random
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())
Another way of resolving the TypeError: argument 1 has unexpected type 'NoneType is to prefix your slot with lambda: like so:
self.tableWidget.cellChanged['int','int'].connect(lambda:self.somefunction())
I actually don't know why but it's a solution that worked for me.

Categories

Resources