PyQt5 integrated ROS Subscriber - qt

How to update PyQt5 based GUI with data came from ROS subscriber.
Hi everyone,
I have a subscriber node and this subscriber gets some string data from a selected topic on GUI. I use a comboBox for topic selection. There is a button for start the subscribtion. I can send the data to terminal with loginfo function. But i want to send the data to a textEdit. When i do that there is a error about threads. Because the callback of the subscriber and GUI are working in different threads. I know that i can use Qthread but do you have any examples or ideas about that. How can i merge this code with Qthread or how can i update GUI with data came from subscriber.
import rospy
from PyQt5 import QtCore, QtGui, QtWidgets
from std_msgs.msg import String
import time
class Ui_MainWindow(object):
def my_node(self):
self.topic = self.comboBox.currentText()
self.sb = rospy.Subscriber(self.topic,String,self.callback_diag,queue_size=1)
rate = rospy.Rate(10)
while(not rospy.is_shutdown):
rate.sleep()
def callback_diag(self,data):
if self.topic != self.comboBox.currentText():
self.sb.unregister()
time.sleep(1)
#self.textEdit_diag.setText(str(data)+ "\n") when i use this code i get the error
rospy.loginfo(data)
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1129, 732)
. #about GUI design
. #about GUI design
. #about GUI design
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
. #about GUI design
. #about GUI design
. #about GUI design
self.pushButton.clicked.connect(lambda: self.my_node())
if __name__ == "__main__":
import sys
rospy.init_node("my_node")
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

Related

Can you embed an external application in PyQt5 GUI on OSX

I'm working on an application that runs visualization using 3rd party software, and sending commands and doing other things through the PyQt5 GUI. At this point, both the PyQt5 window and the visualization run on separate windows, but I'd like to at some point merge them so that it looks like one window. To do that, I'm trying to make a simple example work. Here is code that spawns the calculator app on Mac and a simple window that has some text labels in it:
import os
import sys
import subprocess
import atexit
from PyQt5 import QtWidgets
class CalcWindow(QtWidgets.QMainWindow):
def __init__(self):
super(CalcWindow, self).__init__()
self.setWindowTitle("Embedded Calc")
lines_widget = LinesWidget()
self.setCentralWidget(lines_widget)
calc_path = "/Applications/Calculator.app/Contents/MacOS/Calculator"
print("Path = " + calc_path)
print("Exists: " + str(os.path.exists(calc_path)))
p = subprocess.Popen(calc_path)
atexit.register(self.kill_proc, p)
#staticmethod
def kill_proc(proc):
try:
proc.terminate()
except Exception:
pass
class LinesWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.vLayout = QtWidgets.QVBoxLayout()
self.line1 = QtWidgets.QHBoxLayout()
self.line1.addWidget(QtWidgets.QLabel("HELLO"))
self.line1.addWidget(QtWidgets.QLabel("WORLD"))
self.line2 = QtWidgets.QHBoxLayout()
self.line2.addWidget(QtWidgets.QLabel("SUP"))
self.line2.addWidget(QtWidgets.QLabel("DAWG"))
self.vLayout.addLayout(self.line1)
self.vLayout.addLayout(self.line2)
self.setLayout(self.vLayout)
def main():
app = QtWidgets.QApplication(sys.argv)
calc = CalcWindow()
calc.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I'd like the calculator to be apart of the same window as the Text Labels which are in the main window. I've seen it done on the Windows using hwnd , but I don't know how to directly translate that to OSX or if it's even possible. Maybe there's some PyQt magic of throwing the executable in a widget and adding it to the main window that would work. Not sure. Any help would be appreciated. Thanks.

Get application quit signal before windows are closed in QT

I have a Qt Application with multiple main windows and want to get a signal when the App is about to quit while all windows are still up.
The QApplication.aboutToQuit signal doesn't work for me because it only fires after all windows have been closed.
All other answers I've seen suggest implementing the main window's closeEvent() function, but I have multiple main windows and I couldn't find any way to differentiate CloseEvents between a normal closeEvent of just a single window being closed and a close event from when the whole application is closing after QMD+Q or the using clicking quit in the task bar or for whatever reason it quits.
How do I get a signal for that only when the whole application is about to quit but before any windows are closed?
What happens when I press Cmd+Q or right click quit the task bar icon:
(I want a signal at this point) <-
all windows get a closeEvent()
aboutToQuit() fires
app quits
What I want is to get a signal before any of that happens and all the windows are still open.
Edit: minimal example
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QApplication, QWidget
class Win(QWidget):
def closeEvent(self, event: QCloseEvent):
# implementing a custom closeEvent doesn't help me
# this is called for every normal manual window close and when the application quits
# I only want to run something when the full application gets shut down, not just this window
# can't see any way to differentiate between the two
print("closeEvent", event.type(), event)
return super().closeEvent(event)
if __name__ == '__main__':
app = QApplication([])
app.aboutToQuit.connect(lambda :print("about to quit, this is too late, by now all windows are closed"))
#What I need
# app.beforeQuitSignal.connect(lambda :print("signal that it's gonna quit, before any of the windows are closed"))
#stuff I've tried that didn't work either
app.quit = lambda *args:print("quit, doesnt get called")
app.exit = lambda *args:print("exit, doesnt get called")
app.closeAllWindows = lambda *args:print("closeAllWindows, doesnt get called")
mainwins = []
for i in range(5):
win = Win()
win.setGeometry(100*i,100*i, 400,400), win.show(), win.raise_()
mainwins.append(win)
app.exec_()
If you want to fire a signal when the user close a window but do not exit the QApplication immediately after the last window is closed, you can use QApplication.setQuitOnLastWindowClosed()
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QApplication, QWidget
class Win(QWidget):
closing = pyqtSignal()
def closeEvent(self, event: QCloseEvent):
print("Window {} closed".format(self))
self.closing.emit()
return super().closeEvent(event)
class MyApp(QApplication):
def __init__(self, *args):
super(MyApp, self).__init__(*args)
self.setQuitOnLastWindowClosed(False)
self.lastWindowClosed.connect(self.onLastClosed)
self.mainwins = []
for i in range(5):
win = Win()
win.setGeometry(100 * i, 100 * i, 400, 400), win.show(), win.raise_()
self.mainwins.append(win)
def onLastClosed(self):
print("Last windows closed, exiting ...")
self.exit()
if __name__ == '__main__':
app = MyApp([])
app.exec_()
With this, when a window is closed, a closing() signal is emited
You can filter on the CloseEvent if it is spontaneous :
from PyQt5.QtCore import pyqtSignal, QEvent
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QApplication, QWidget
class Win(QWidget):
closing = pyqtSignal()
def closeEvent(self, event: QCloseEvent):
self.closing.emit()
if event.type() == QEvent.Close and event.spontaneous():
event.ignore()
else:
return super().closeEvent(event)
class MyApp(QApplication):
def __init__(self, *args):
super(MyApp, self).__init__(*args)
self.mainwins = []
for i in range(5):
win = Win()
win.closing.connect(self.beforeQuit)
win.setGeometry(100 * i, 100 * i, 400, 400), win.show(), win.raise_()
self.mainwins.append(win)
def beforeQuit(self):
print("Exiting")
# Do what you want here ...
self.exit() # Terminate the QApplication
if __name__ == '__main__':
app = MyApp([])
app.exec_()
Use installEventFilter to intercept the application's quit event before any windows are closed:
class AppManager(QObject):
def __init__(self):
super().__init__()
qApp.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() == QEvent.Quit:
self.saveWindowStates()
return False
def saveWindowStates(self):
# code to save the application's state

How to close Pyqt5 program from QML?

I started a project in Qt Creator initially with a C++ backend, but then switched it to use PyQt5. I have a main.qml, where when I press a button called Exit, I call Qt.quit().
However, I get a General Message stating: Signal QQmlEngine::quit() emitted, but no receivers connected to handle it.
My question is, how do I receive this signal and handle it?
Code:
main.py:
import sys
import PyQt5
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtQml
from PyQt5.QtCore import QObject pyqtSignal
class DestinyManager,(QtGui.QGuiApplication):
"""the app self"""
def __init__(self, argv):
super(DestinyManager, self).__init__(argv)
# Define a new signal called 'trigger' that has no arguments.
trigger = pyqtSignal()
def connect_and_emit_trigger(self):
# Connect the trigger signal to a slot.
self.trigger.connect(self.handle_trigger)
self.menuItem_Exit.clicked.connect(self.close)
# Emit the signal.
self.trigger.emit()
def handle_trigger(self):
# Show that the slot has been called.
print("trigger signal received")
def main(argv):
app = DestinyManager(sys.argv)
engine = QtQml.QQmlEngine(app)
component = QtQml.QQmlComponent(engine)
component.loadUrl(QtCore.QUrl("exit.qml"))
topLevel = component.create()
if topLevel is not None:
topLevel.show()
else:
for err in component.errors():
print(err.toString())
app.exec()
if __name__ == '__main__':
QObject,main(sys.argv)
Exit.qml:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
Window {
Button {
id: btn_Exit
text: "Exit"
onClicked: Qt.quit();
}
}
There are a few syntax errors in the python script, but ignoring those, the code can be made to work like this:
def main(argv):
app = DestinyManager(sys.argv)
engine = QtQml.QQmlEngine(app)
engine.quit.connect(app.quit)
...
Which is to say, you simply need to connect the qml quit signal to an appropriate slot in your python script.

PyQt: connecting slots with custom objects via Qt Designer

I have a PyQt widget that sends signals with numpy.ndarray data. And I have another PyQt widget that has a slot with numpy.ndarray data.
Both widget are located on my main window, that is compiled from *.ui file. The widgets are set as promoted widgets.
Cannot I somehow connect the signal and slot in Qt Creator?
Just now it gives me the next error:
TypeError: C++ type 'ndarray' is not supported as a slot argument type
Reason for this is Qt only support the datatype defined in QMetaType passed as argument of signal, looks here http://pyqt.sourceforge.net/Docs/PyQt4/qmetatype.html#Q_DECLARE_METATYPE
According to ekhumoro's POST, I update the following code, it should work for PyQt4, not tested on PyQt5.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from numpy import *
class MyThread(QThread):
mysignal = pyqtSignal(ndarray)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
def run(self):
while True:
QThread.msleep(100)
self.mysignal.emit(array((1, 2, 3, 4)))
class MyWidget(QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.thread = MyThread()
self.thread.mysignal.connect(self.handleSignal)
self.thread.start()
def handleSignal(self, data):
print data
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = MyWidget()
w.show()
app.exec_()

Single QTreeWidgetItem instance on multiple QTreeWidget?

I'm trying to show a single QTreeWidgetItem instance on 2 QTreeWidgets, which ends up the item shown only on the 1st tree without being notified. I haven't seen its API doc talks about a limitation if any. Is there a way to workaround?
#!/usr/bin/python
import os
import sys
from PySide.QtCore import QFile
from PySide.QtUiTools import QUiLoader
from PySide.QtGui import QApplication, QTreeWidget, QTreeWidgetItem, QWidget
class MyTreeWidgetItem(QTreeWidgetItem):
def __init__(self, *args):
super(MyTreeWidgetItem, self).__init__()
class MyWidget(QWidget):
def __init__(self, *args):
super(MyWidget, self).__init__()
loader = QUiLoader()
file = QFile('./src/prove_qtreewidget/qtree_mainwidget.ui')
file.open(QFile.ReadOnly)
self.widget_ui = loader.load(file, self)
file.close()
item1 = MyTreeWidgetItem(self)
item1.setText(0, 'Item 1')
_tw1 = self.widget_ui.findChild(QTreeWidget, '_tree_widget_1')
_tw2 = self.widget_ui.findChild(QTreeWidget, '_tree_widget_2')
_tw1.addTopLevelItem(item1)
_tw2.addTopLevelItem(item1)
if __name__ == '__main__':
print("Running in " + os.getcwd() + " .\n")
app = QApplication(sys.argv)
win = MyWidget()
win.show()
app.exec_()
.ui file above is available here.
Using Qt 4.8, Ubuntu 12.04
I haven't tried PyQt binding but I just assume shouldn't be any different w/o proof.
What you need is a model and a QTreeView, that's for what they are for:
Model/View Programming: Widgets do not maintain internal data containers. They access external data through a standardized interface and therefore avoid data duplication.

Resources