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.
Related
Running this code:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
path = "video.mp4"
video_widget = qtmw.QVideoWidget()
video_widget.show()
media_player = qtm.QMediaPlayer()
media_player.setVideoOutput(video_widget)
media_player.mediaStatusChanged.connect(media_player.play)
media_player.setSource(path)
qtc.QTimer.singleShot(2000, lambda: media_player.setSource(''))
app.exec()
, you will notice that when media_player.setSource('') is called after 2000 milliseconds, the GUI becomes unresponsive for a good second or so. And it seems like it is not possible to move only the setSource() call to a separate thread, I get a warning saying QObject::killTimer: Timers cannot be stopped from another thread (you can replicate this by replacing qtc.QTimer... call with qtc.QThreadPool.globalInstance().start(lambda: media_player.setSource(''))). So, after hours of pondering, as a work around, I moved the entire QMediaPlayer instance and all objects related to it to a thread of its own and used QVideoSink's videoFrameChanged signal to update the video on the video widget:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
path = "video.mp4"
class VideoPlayer(qtmw.QVideoWidget):
_stop_worker_signal = qtc.Signal()
_initialize_media_player_signal = qtc.Signal()
_set_media_source_signal = qtc.Signal(str)
_play_media_signal = qtc.Signal()
_stop_media_signal = qtc.Signal()
_pause_media_signal = qtc.Signal()
def __init__(self) -> None:
super().__init__()
self._worker = Worker()
self._worker_thread = qtc.QThread()
self._worker.moveToThread(self._worker_thread)
self._stop_worker_signal.connect(self._worker.stop)
self._initialize_media_player_signal.connect(self._worker.create_media_player)
self._worker.video_sink_frame_changed_signal.connect(lambda frame: self.videoSink().setVideoFrame(frame))
self._set_media_source_signal.connect(self._worker.set_media_source)
self._stop_media_signal.connect(self._worker.stop_media)
self._worker_thread.start()
self._initialize_media_player_signal.emit()
self._set_media_source_signal.emit(path)
self.show()
def keyPressEvent(self, event):
if event.key() == qtc.Qt.Key.Key_Q:
self._set_media_source_signal.emit('')
def closeEvent(self, event: qtc.QEvent):
loop = qtc.QEventLoop()
self._worker.stopped_signal.connect(loop.exit)
self._stop_worker_signal.emit()
loop.exec()
self._worker_thread.exit()
class Worker(qtc.QObject):
video_sink_frame_changed_signal = qtc.Signal(qtm.QVideoFrame)
stopped_signal = qtc.Signal()
def create_media_player(self):
self._audio_output = qtm.QAudioOutput(qtm.QMediaDevices.defaultAudioOutput())
self._video_sink = qtm.QVideoSink()
self._video_sink.videoFrameChanged.connect(self.video_sink_frame_changed_signal)
self._media_player = qtm.QMediaPlayer()
self._media_player.setAudioOutput(self._audio_output)
self._media_player.setVideoSink(self._video_sink)
self._media_player.mediaStatusChanged.connect(self._media_player.play)
def set_media_source(self, source: str) -> None:
self._media_player.setSource(source)
def stop_media(self) -> None:
if not self._media_player.playbackState() == qtm.QMediaPlayer.PlaybackState.StoppedState:
self._media_player.stop()
def stop(self):
self._media_player.stop()
self._audio_output.deleteLater()
self._video_sink.deleteLater()
self._media_player.deleteLater()
self.stopped_signal.emit()
app = qtw.QApplication()
vp = VideoPlayer()
app.exec()
Now, there's no unresponsiveness in the GUI when setSource() is called but, as one can see, it's a pretty convoluted way to do this. Is there a better way?
I'm trying to run a simple PyQt5 application on Linux, the code is as follows:
#!/usr/bin/python
import sys
from PyQt5.QtWidgets import QApplication, QWidget
def main():
app = QApplication(sys.argv)
w = QWidget()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
mime = app.clipboard().mimeData()
print(mime.hasImage()) # True
print(mime.imageData()) # None
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Before running it, I copied an image into the clipboard, so mime.hasImage() should return True. No problem, that's also the case. But what's weird is, mime.imageData() sometimes returns None. that shouldn't happen. mime.imageData() should contain the image that I copied instead of None. Is there anything wrong with the code?
By the way, this seems to only happen on Linux, mime.imageData() never returns None on Windows. I'm using python3
That hasImage() returns True does not imply that imageData() returns a QImage since it only indicates that the user copied an image to the clipboard, and in what format do I copy the image? Well, it could be png, jpg, etc or it could provide the url for the client application to download or html to insert it into the client application and then obtain the image by rendering the HTML.
So in general the application from which the image was copied is responsible for the sending format and that there is no restrictive standard for that format but there are common formats.
The following example shows the logic to handle the images that come from urls and HTML:
#!/usr/bin/python
import sys
from functools import cached_property
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtGui import QGuiApplication, QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from bs4 import BeautifulSoup
class ImageDownloader(QObject):
finished = pyqtSignal(QImage)
def __init__(self, parent=None):
super().__init__(parent)
self.manager.finished.connect(self.handle_finished)
#cached_property
def manager(self):
return QNetworkAccessManager()
def start_download(self, url):
self.manager.get(QNetworkRequest(url))
def handle_finished(self, reply):
if reply.error() != QNetworkReply.NoError:
print("error: ", reply.errorString())
return
image = QImage()
image.loadFromData(reply.readAll())
self.finished.emit(image)
class ClipboardManager(QObject):
imageChanged = pyqtSignal(QImage)
def __init__(self, parent=None):
super().__init__(parent)
QGuiApplication.clipboard().dataChanged.connect(
self.handle_clipboard_datachanged
)
self.downloader.finished.connect(self.imageChanged)
#cached_property
def downloader(self):
return ImageDownloader()
def handle_clipboard_datachanged(self):
mime = QGuiApplication.clipboard().mimeData()
if mime.hasImage():
image = mime.imageData()
if image is not None:
self.imageChanged.emit(image)
elif mime.hasUrls():
url = mime.urls()[0]
self.downloader.start_download(urls[0])
elif mime.hasHtml():
html = mime.html()
soup = BeautifulSoup(html, features="lxml")
imgs = soup.findAll("img")
if imgs:
url = QUrl.fromUserInput(imgs[0]["src"])
self.downloader.start_download(url)
else:
for fmt in mime.formats():
print(fmt, mime.data(fmt))
def main():
app = QApplication(sys.argv)
label = QLabel(scaledContents=True)
label.resize(250, 150)
label.move(300, 300)
label.setWindowTitle("Simple")
label.show()
manager = ClipboardManager()
manager.imageChanged.connect(
lambda image: label.setPixmap(QPixmap.fromImage(image))
)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
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.
I have two python files, one is the gui, the other contains all of the functions, say i have a class named Backend like in the code below, and one of its functions is "print1(self)", and i want to use that "print1" function inside of the second file in the class named retranslateUi(self, Form), how would i do it?
First file
__author__ = 'Richard'
import sqlite3
conn = sqlite3.connect('realscheduler.db')
c = conn.cursor()
c.execute('pragma foreign_keys = ON;')
class Backend:
def print1(self):
print('Works')
Second file
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'untitled.ui'
#
# Created by: PyQt5 UI code generator 5.4.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.horizontalLayout = QtWidgets.QHBoxLayout(Form)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.label = QtWidgets.QLabel(Form)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem1)
self.horizontalLayout.addLayout(self.verticalLayout)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "Labellabellabel"))
self.pushButton.setText(_translate("Form", "Print"))
First you should create a main entry point to the application like so. I will call it app.py and the generated Qt Designer file view.py for this example:
from PyQt5.QtWidgets import (QMainWindow, QApplication)
# importing the GUI from the designer file
from view import Ui_MainWindow
# importing the database file Backend class
from database import Backend
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
# this will connect your push button the the function
self.pushButton.clicked.connect(Backend.print1)
if __name__ == "__main__":
import sys
QCoreApplication.setApplicationName("app name")
QCoreApplication.setApplicationVersion("app version")
QCoreApplication.setOrganizationName("Your name")
QCoreApplication.setOrganizationDomain("Your URL")
app = QApplication(sys.argv)
form = Main()
form.show()
sys.exit(app.exec_())
PyQt follows the Model-View architecture so this is a nice way to bind your functionality / data away from the graphical components of the application.
You have to import the first file into the second one as import first_filename as t then create class object p=t.classname then use any function you want which is declared in the class
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_()