I have a PyQt program that hosts 2 widgets. The idea is to interface with my Arduino and display the Arduinos info in the program. I can't really attach my whole program but I'll give the highlights.
The Arduino takes a command over serial via ser.write and returns the subsequent information using ser.read()
So a simple function to continuously read the information from the Arduino would be
while True:
ser.write(command.encode()
time.sleep(.1)
resp=ser.read()
data=struct.unpack('<b',resp)
Now I want to use the information in data in my PyQt program however I cannot continuously run a loop in my Qt program because it will never display the program. I've tried using QThread by making a demo program however it crashes with error QThread: Destroyed while thread is still running. This is my demo program which should have similar functionality to the actual program.
import sys
import urllib
import urllib.request
import serial
import time
from PyQt4 import QtCore, QtGui
class CmdThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
BASIC="\x24\x4d\x3c\x00"
self.ser=serial.Serial()
#ser.port="COM12"
self.ser.port='COM12'
self.ser.baudrate=115200
self.ser.bytesize = serial.EIGHTBITS
self.ser.parity = serial.PARITY_NONE
self.ser.stopbits = serial.STOPBITS_ONE
self.ser.timeout = 0
self.ser.xonxoff = False
self.ser.rtscts = False
self.ser.dsrdtr = False
self.ser.writeTimeout = 2
self.ser.open()
print('Initializing in 10 seconds...')
time.sleep(10)
def run(self):
self.ser.write(self.BASIC.encode())
time.sleep(0.1)
resp=self.ser.read()
datalength=struct.unpack('<b',resp)[0]
data=self.ser.read(datalength+1)
data=data[4:-1]
temp=struct.unpack('<'+'h'*(int(len(data)/2)),data)
self.ser.flushInput()
self.ser.flushOutput()
print((temp[0]/10,temp[1]/10,temp[2]))
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_cmd)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def start_cmd(self):
downloader = CmdThread()
downloader.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Can anyone explain why this is happening or maybe a solution on how to incorporate the simpler while loop into a Qt widget? Thanks so much!
You need to store a reference to the QThread so that it isn't garbage collected. Like this:
def start_cmd(self):
self.downloader = CmdThread()
self.downloader.start()
However be warned that clicking the button a second time will replace the original reference with a new one. And so the first thread may get garbage collected (which is probably fine if it is finished). You may want to eventually consider a more complex architecture where you always have a thread running and send commands from the main thread to the arduino thread via Qt signals/slots.
On another note, I assume at some point you will be using the results from the arduino to update a widget. Please make sure you don't directly access the widget from the thread (Qt GUI objects should only ever be accessed from the main thread). Instead you need to emit a signal (with a parameter that contains your data) from the thread which connects to a slot in the main thread. This slot is then able to safely access the GUI objects.
This shows a more complex example with two way communication between the main thread and a worker thread (albeit without sending data along with the signal emission, but that is a reasonably trivial change)
Related
I have an application where i should use QWebSocketServer to accept and parse some socket messages coming from a Bokeh Application. I was able to do that up to now, but now i want to move this whole QWebSocketServer into another thread, so that it wont bother the main GUI. The main Question being does the QWebSocketServer in anyway affect GUI?
The GUI being the parent for QWebSocketServer, i was not able to move it to another thread. Will this work if i inherit QThread class?
I know i should not inherit QThread class and overload run method. I am just wondering if this is possible.
FYI : I am doing all of this in Raspberry pi, and raspberry pi has to do lot of data collection.
I initialized the QWebSocketServer without parent and moved it to another thread and it worked. I don't know if im doing right.
Im doing like this :-
self.server = SocketServer()
self.serverThread = QThread()
self.server.moveToThread(self.serverThread)
Here SocketServer is the server class that inherits QObject(), and QWebSocketServer gets defined in it, with out any parent.
Here is a short snippet of SocketServer class:-
onMessageRecieved = pyqtSignal(str)
serverObject = QWebSocketServer("My Server",QWebSocketServer.NonSecureMode)
def __init__(self,parent=None):
super().__init__(parent)
self.server = QWebSocketServer(self.serverObject.serverName(),self.serverObject.secureMode(),parent)
and dont forget to start the thread.
I have come up with a simple signal/slot mechanism below. The signal is called by QSlider when the value is changed via QSlider::valueChanged(). And the slot is called via the QLCDNumber::display() method.
What is confusing to me is why PyQt5 has so little documentation and most documentation leads to links for Qt5. The specific issue I have with the code is:
1) If QSlider::valueChanged() (signal) expects an integer as a parameter why do we only pass in QLCDNumber::display() (slot) which is a void function. Therefore nothing is going to be passed in.
2) In the commented-out code below, I am not able to call a second slot. Is there a limit to the number of slots you can call for 1 signal? If there is how do I go about calling multiple slots in PyQt5.
Edit: I believe because printLabel() is not a defined slot, this is why I am getting issues. Is it possible for me to include any amount of slots in the ::connect() parameters? Or am I approaching this in a hacky way.
import sys
from PyQt5.QtCore import (Qt)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
QVBoxLayout, QApplication)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def printLabel(self, str):
print(str)
def initUI(self):
lcd = QLCDNumber(self)
sld = QSlider(Qt.Horizontal, self)
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(sld)
self.setLayout(vbox)
#This line works
sld.valueChanged.connect(lcd.display)
#This line does not work
#sld.valueChanged.connect(lcd.display, self.printLabel("hi"))
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Taking your points in order:
The Qt5 documentation contains at least 90% of what you need to know, so there's little point in repeating it all in the PyQt5 documentation, which instead mainly concentrates on the features that are specific to PyQt (see the Signals and Slots article, for instance). Qt5 is a C++ library. You don't need to know C++ in order to use PyQt (or to read Qt's excellent documentation), but you can hardly expect to avoid it altogether.
The QSlider.valueChanged signal sends an int value to all the slots that are connected to it. The QLCDNUmber.display slot receives either a string, int or float object (i.e. it has three overloads). For built-in Qt slots, there must be an overload that matches what the signal sends. But user-defined slots in PyQt can be any python callable object, and it doesn't matter if the signature doesn't match (any extra parameters will be thrown away). Having said that, an explicit signature can be defined for a user-defined slot by using the pyqtSlot decorator (which therefore allows multiple overloads to be specified in PyQt).
As explained in the first section of the PyQt article mentioned above, both signals and slots can have multiple connections. The article also details the PyQt-specific APIs (including pyqtSlot), and gives examples of how to use them.
Here's how to connect to multiple slots your example:
class Example(QWidget):
...
def initUI(self):
...
#connect to a built-in slot
sld.valueChanged.connect(lcd.display)
#connect to a user-defined lot
sld.valueChanged.connect(self.printLabel)
#any python callable will do
sld.valueChanged.connect(lambda x: print('lambda:', x))
...
def printLabel(self, value):
print('printLabel:', value)
I have QWidgets lined up in a QVBoxLayout inside a QScrollArea with setWidgetResizable(True)
When I resize one of the QWidgets, the QWidgets relocate themselves accordingly in the graphical user interface, but their geometry() property doesn't reflect that, geometry().x() and geometry().y() both remain the same before and after relocation.
The only method I have found so far to update the x() and y() coordinates is to hide() and show() the QScrollArea.
I have tried update(), updateGeometry() and repaint() without any success.
Here is a sample test code that I made to summarize the problem :
import sys
from PySide import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.showMaximized()
self.widget_window = QtGui.QWidget()
self.scrollarea = QtGui.QScrollArea()
self.v_layout = QtGui.QVBoxLayout()
self.test_1 = QtGui.QPushButton('Test 1')
self.test_2 = QtGui.QPushButton('Test 2')
self.v_layout.addWidget(self.test_1)
self.v_layout.addWidget(self.test_2)
self.widget_window.setLayout(self.v_layout)
self.scrollarea.setWidgetResizable(True)
self.scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scrollarea.setAlignment(QtCore.Qt.AlignCenter)
self.scrollarea.setWidget(self.widget_window)
self.setCentralWidget(self.scrollarea)
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
self.test_1.setFixedHeight(1000)
#uncommenting the following next two lines, solves the problem
#self.scrollarea.hide()
#self.scrollarea.show()
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
def main():
app = QtGui.QApplication(sys.argv)
main = MainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
My questions are :
Is there a better solution ?
Isn't updateGeometry() supposed to actually update the geometry() ?
Why is this behavior happening ? (i.e why does it update graphically but not programmatically ?)
Can using hide() and show() successively cause some problems in other contexts (window flickering or something else...) ?
When you update the GUI, it's not instantaneous. It's performed when you give the control to the event loop. In this case, it's done after __init__ completes, which is obviously after all your prints. Therefore, you'll see the old position.
hide/show forces an update at that point. That's why you get correct values. However, this is not the best way, because you might observe flicker while hide/show does its job.
Better way would be telling the event loop to process events before continuing. This will ensure that the GUI is updated:
#...
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
self.test_1.setFixedHeight(1000)
QtGui.QApplication.processEvents()
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
#...
PS: I'm assuming, you have good reasons to do this. Most of the time, you won't care when the GUI is updated.
I've built an app for a game, simple to start. It's a game in which the system randomly chooses a number and a gamer (player) tries to find out the number. Everything is almost done. The app consists of a QLineEdit, a label and three buttons. Once the app tells the player the range of the wanted number, he/she types a bet_number and clicks on the play button. And according to this number he/she gets a message about how close or far the wanted number is away from the bet_number.
But I find it a little disgusting to click a button. Instead I want to use Enter key to play. So to achieve this, it comes down to specifically two questions:
How could one change to using Enter to play (I mean I need know when QLineEdit detects enter key is pressed)? In this way I'll code properly to point the play method.
If the play button's got the focus, how do you use enter key on this button? (make Button accept Enter key)
For the QLineEdit connect to the returnPressed signal.
Alternatively, if you use the setAutoDefault method on your QPushButtons you emit the clicked signal when Enter is pressed on a focused QPushButton:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButtonOK = QtGui.QPushButton(self)
self.pushButtonOK.setText("OK")
self.pushButtonOK.clicked.connect(self.on_pushButtonOK_clicked)
self.pushButtonOK.setAutoDefault(True)
self.lineEditNumber = QtGui.QLineEdit(self)
self.lineEditNumber.returnPressed.connect(self.pushButtonOK.click)
self.layoutHorizontal = QtGui.QHBoxLayout(self)
self.layoutHorizontal.addWidget(self.pushButtonOK)
self.layoutHorizontal.addWidget(self.lineEditNumber)
#QtCore.pyqtSlot()
def on_pushButtonOK_clicked(self):
inputNumber = self.lineEditNumber.text()
if inputNumber.isdigit():
info = "You selected `{0}`"
else:
info = "Please select a number, `{0}` isn't valid!"
print info.format(inputNumber)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
QLineEdit will emit the signal returnPressed() whenever the user presses the enter key while in it: http://qt-project.org/doc/qt-4.8/qlineedit.html#signals. You can either connect this signal to your button's click() slot or directly call whatever your button's clicked() signal was connected to.
A slight C++ variation on other answers, it's not significantly different, but I thought i would include it anyway because how you lay things in QT code can be very different from codebase to codebase and I wanted to strip out the extraneous stuff to give the shortest and easiest to understand excerpt of code.
QLineEdit *TextSend = new QLineEdit("");
QPushButton *SendPB = new QPushButton("Send!");
connect(TextSend, &QLineEdit::returnPressed, this, &CLITab::SendCommand);
connect(SendPB, &QPushButton::released, this, &CLITab::SendCommand);
So what this is doing is we create a QLineEdit textbox and a QPushbutton.
We do cosmetic things like set the string label for them and add them to our layout.
Then we setup a callback handler, which will be triggered when the QLineEdit returns "returnPressed", which then calls automatically into a function which i wrote called "CLITab::SendCommand()", and then it's upto this function to extract the data out of QLineEdit and do whatever needs to be done. In practice the TextSend and SendPB pointers would live in the parent class, so that SendCommand() has visibility over these objects.
Just putting this here, along side an example pushbutton, because essentially they work in precisely the same way, all that's different is the signal name emitted.
Just started learning PySide and is having problem with QTimer
I have this
#!/usr/bin/python
from PySide.QtCore import QThread;
from classes import Updater;
if __name__ == "__main__":
thread = QThread();
thread.start();
update = Updater();
update.moveToThread(thread);
update.run();
and this
class Updater(QObject):
def update_mode(self):
#do something
pass;
def run(self):
timer = QTimer();
timer.timeout.connect(self.update_mode);
timer.start(10);
I want my script to do some work periodically using QTimer (wanted to try QSystemAlignedTimer but that looks even more complicated to me for now...). Not sure what is wrong at the moment because I am getting this error
QObject::startTimer: QTimer can only be used with threads started with QThread
QEventLoop: Cannot be used without QApplication
QThread: Destroyed while thread is still running
QTimer, along with all other event based classes, need there to be a QApplication instance.
In:
thread = QThread();
thread.start();
update = Updater();
update.moveToThread(thread);
update.run();
First of all, get rid of the semicolons. Python programmers don't like those.
If you take a close look at what your code is doing, you are making a QThread, starting it, making an Updater, moving it to the thread, running it, and ending the program. There is no command here telling Python to keep the program running, so it ends, and the QThread complains about being destroyed.
What you should do is make a QApplication, with something like
app = QApplication(sys.argv)
and the call app.exec_() to start it. In this case, this is essentially equivalent to time.sleep(9999999999...), but what it actually does is it processes events (signals/slots) endlessly. With time.sleep(9999999999...), QTimers will never do anything when they time out.
As a QApplication is an infinite loop, you will have to manually exit within your code.