I'm trying to write a program that will interact with QGraphicsView. I want to gather mouse and keyboard events when the happen in the QGraphicsView. For example, if the user clicks on the QGraphicsView widget I will get the mouse position, something like that. I can hard code it rather easily, but I want to use QtDesigner because the UI will be changing frequently.
This is the code that I have for the gui.py. A simple widget with a QGraphicsView in it.
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_graphicsViewWidget(object):
def setupUi(self, graphicsViewWidget):
graphicsViewWidget.setObjectName(_fromUtf8("graphicsViewWidget"))
graphicsViewWidget.resize(400, 300)
graphicsViewWidget.setMouseTracking(True)
self.graphicsView = QtGui.QGraphicsView(graphicsViewWidget)
self.graphicsView.setGeometry(QtCore.QRect(70, 40, 256, 192))
self.graphicsView.setObjectName(_fromUtf8("graphicsView"))
self.retranslateUi(graphicsViewWidget)
QtCore.QMetaObject.connectSlotsByName(graphicsViewWidget)
def retranslateUi(self, graphicsViewWidget):
graphicsViewWidget.setWindowTitle(QtGui.QApplication.translate("graphicsViewWidget", "Form", None, QtGui.QApplication.UnicodeUTF8))
The code for the program:
#!/usr/bin/python -d
import sys
from PyQt4 import QtCore, QtGui
from gui import Ui_graphicsViewWidget
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_graphicsViewWidget()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.graphicsView, QtCore.SIGNAL("moved"), self.test)
def mouseMoveEvent(self, event):
print "Mouse Pointer is currently hovering at: ", event.pos()
self.emit(QtCore.SIGNAL("moved"), event)
def test(self, event):
print('in test')
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
When I run this code, it gives me the opposite of what I want. I get the mouse position everywhere except for inside the QGraphicsView.
I'm sure it's a problem with my QObject.connect. But every time I go back and read about signals and slots it makes sense but I can't get it.
Please help, I've been banging my head for the past few days now. I'm sorry if this as been asked before but I've been through all the threads on this topic and I can't get anywhere.
Thanks
The signal must come from the QGraphicsView object that was defined in the ui.
You can create a class derived from QGraphicsView like this
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyView(QGraphicsView):
moved = pyqtSignal(QMouseEvent)
def __init__(self, parent = None):
super(MyView, self).__init__(parent)
def mouseMoveEvent(self, event):
# call the base method to be sure the events are forwarded to the scene
super(MyView, self).mouseMoveEvent(event)
print "Mouse Pointer is currently hovering at: ", event.pos()
self.moved.emit(event)
Then, in the designer:
right-click on the QGraphicsView then Promote to
write the class name in the Promoted Class Name field (e.g. "MyView"),
write the file name where that class is in the Header file field but without the .py extension,
click on the Add button and then on the Promote button.
And you can regenerate your file gui.py with pyuic4.
Related
I use this library https://github.com/jaseg/python-mpv and PySide6 to embed MPV in container (QWidget) in python. I want to draw the button (or something else maybe qlabel for example?) over QWidget (embedded mpv) on top of player but how can I do that? I tried to draw button but when mpv starts playing player overrides the button with itself. How to place button on top of player?
Update:
I read here that I should use opengl as render api.:https://github.com/mpv-player/mpv-examples/blob/master/libmpv/README.md
Update 2:
Maybe i need to use qml?
How can I use opengl along with pyside6 (or pyqt5) and mpv to render properly?
My code there:
import os
from PySide6 import QtWidgets
os.environ['PATH'] += os.path.dirname(__file__) #you need to place mpv-2.dll (or mpv-1.dll) in folder with project
import mpv
import sys
from PySide6.QtWidgets import *
from PySide6.QtCore import *
class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(780, 477)
self.pushButton = QPushButton(Form)
self.pushButton.setObjectName(u"pushButton")
self.pushButton.setGeometry(QRect(30, 430, 75, 24))
self.retranslateUi(Form)
QMetaObject.connectSlotsByName(Form)
# setupUi
def retranslateUi(self, Form):
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
self.pushButton.setText(QCoreApplication.translate("Form", u"PushButton", None))
# retranslateUi
class PlayerWidget(QtWidgets.QWidget, Ui_Form):
def __init__(self, parent=None):
super(PlayerWidget, self).__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
class Test(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.container = PlayerWidget()
self.setCentralWidget(self.container)
self.container.setAttribute(Qt.WA_DontCreateNativeAncestors)
self.container.setAttribute(Qt.WA_NativeWindow)
player = mpv.MPV(wid=str(int(self.container.winId())),
vo='gpu',
log_handler=print,
loglevel='debug', ytdl=True)
player.play('test.mp4') # place your test video in folder
app = QApplication(sys.argv)
# This is necessary since PyQT stomps over the locale settings needed by libmpv.
# This needs to happen after importing PyQT before creating the first mpv.MPV instance.
import locale
locale.setlocale(locale.LC_NUMERIC, 'C')
win = Test()
win.show()
win.resize(1280, 720)
sys.exit(app.exec_())
As far as I know this is not possible if you use the window id embedding method.
In order to draw over the video you would need to use the render api.
An example with qml exists: https://github.com/jaseg/python-mpv#using-opengl-from-pyqt5qml
It should also be possible to achieve this using QtWidgets (and not Qml).
It might be a very simple question but I can't figure out how to do it.
The context:
I am making an application that can draw polygons in a QGraphicsScene. I would like to implement interactive actions. For example, the user clicks on a button, then clicks on the scene to draw temporary lines, finally the app cuts the polygon with the line.
What I try to achieve
What I did
Since I didn't find anything here or in the doc, I looked in the LibreCad repository to know how they do it.
https://github.com/LibreCAD/LibreCAD/blob/768285653c46e734a75460928142cda1a33c2c53/librecad/src/lib/actions/rs_actioninterface.h
Their base class inherits from QObject to handle events. It seems that they don't inherit from QAction despite line 40 where one can read "class QAction;". My C++ skills end here.
In an act of faith, I wrote the following minimal non-working example in python with PySide.
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PySide2.QtCore import QObject
from PySide2.QtWidgets import (QApplication, QMainWindow, QWidget,
QGraphicsView, QGraphicsScene,
QToolBar, QAction)
class Action(QAction, QObject):
def __init__(self, name, parent=None):
super().__init__(name, parent)
# I also tried to add this, obviously I don't know what I am doing
#QObject.__init__(parent)
def keyPressEvent(self, event):
print("Event handled")
super().keyPressEvent(event)
class MainWindow(QMainWindow,):
def __init__(self):
super(MainWindow, self).__init__()
self.centralwidget = QWidget()
self.graphicsView = QGraphicsView(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.scene = QGraphicsScene(0,0,1000,1000)
self.graphicsView.setScene(self.scene)
toolbar = QToolBar("My toolbar")
self.addToolBar(toolbar)
knifeActionButton = Action("Knife", self)
knifeActionButton.setShortcut("K")
knifeActionButton.triggered.connect(self.cutPolygon)
toolbar.addAction(knifeActionButton)
def cutPolygon(self):
print("Action triggered")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
The question:
How to handle events in a QAction to make it interactive?
Please excuse my English, I am a non-native speaker
How does one properly capture the close event coming out of a PySide QtUiTools.QUiLoader() setup?
I can get the instanced class to connect to widgets and everything else, but I am not sure how to intercept the signals in this setup.
Ideally, I want all close calls to pass through my closeEvent (obviously) so that I can ensure that it's safe to close the window. But since my self.closeEvent() is tied to my View(QtWidgets.QMainWindow) and not the self._qt.closeEvent(), I don't know how to get to the self._qt.closeEvent() method to override it in this case.
Or is there a better way to set this up to capture those window events?
# Compatible enough with Pyside 2
from PySide import QtGui as QtWidgets
from PySide import QtUiTools
from PySide import QtCore
class View(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(View, self).__init__(parent=parent)
self.setup()
def closeEvent(self, event):
# Do things
event.accept()
def setup(self):
loader = QtUiTools.QUiLoader()
fy = QtCore.QFile('example.ui')
fy.open(QtCore.QFile.ReadOnly)
self._qt = loader.load(fy, self)
fy.close()
self._qt.pCanceled.clicked(self._qt.close)
Doesn't apply:
PySide / PyQt detect if user trying to close window
Close, but PySide doesn't use PyQt's uic and appears to run differently (and didn't work):
PyQt: clicking X doesn't trigger closeEvent
closeEvent is not a signal, it is a method that is called when the QCloseEvent event is sent. A signal and an event are different things. Going to the problem, in Qt there are 2 ways to listen to events, the first one is overwriting the fooEvent() methods and the second one using an event filter as I show below:
from PySide import QtGui as QtWidgets
from PySide import QtUiTools
from PySide import QtCore
class View(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(View, self).__init__(parent=parent)
self._qt = None
self.setup()
def closeEvent(self, event):
self.do_things()
super(View, self).closeEvent(event)
def do_things(self):
print("do_things")
def setup(self):
loader = QtUiTools.QUiLoader()
fy = QtCore.QFile('example.ui')
fy.open(QtCore.QFile.ReadOnly)
self._qt = loader.load(fy, self)
fy.close()
self._qt.pCanceled.clicked.connect(self._qt.close)
self._qt.installEventFilter(self)
def eventFilter(self, watched, event):
if watched is self._qt and event.type() == QtCore.QEvent.Close:
self.do_things()
return super(View, self).eventFilter(watched, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
Update:
Normally in the eventFilter it is enough to return True for the event to be ignored but in the case of QCloseEvent you must ignore the event and return True as shown below:
def eventFilter(self, watched, event):
if watched is self._qt and event.type() == QtCore.QEvent.Close:
self.do_things()
event.ignore()
return True
return super(View, self).eventFilter(watched, event)
I have a QMainWindow that launches a QDialog everytime I click on a button and I can't figure out why the python binary crashes when I close the QMainWindow while one or more dialogs are open.
It's not a complex Qt app and I'm really struggling trying to understand what happens.
Here's the code:
# dependency modules
from PyQt4 import QtGui
import sys
# custom modules
from ui import SingleOrderUI, DashBoardUI
class SingleOrder(QtGui.QDialog, SingleOrderUI.Ui_SingleOrder):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
class DashBoard(QtGui.QMainWindow, DashBoardUI.Ui_DashBoard):
def __init__(self):
QtGui.QMainWindow.__init__(self)
super(DashBoard, self).__init__()
# setup UI
self.setupUi(self)
self.newOrderBtn.clicked.connect(self.newOrder)
def newOrder(self):
print 'New order clicked'
so = SingleOrder(self)
so.show()
app = QtGui.QApplication(sys.argv)
window = DashBoard()
window.show()
sys.exit(app.exec_())
Any help would be appreciated.
EDIT: When launched using ipython, the dialogs are still showing after I close the QMainWindow, so that's maybe where the issue comes from.
I give the QMainWindow as a parent argument to the QDialog, I thought that was enough to have them killed when the QMainWindow is closed.
Okay, I've found a workaround for that but I'm not sure if it's the right way to do it.
On my DashBoard init method, I've added a python list that will store all the opened Dialogs:
def __init__(self):
QtGui.QMainWindow.__init__(self)
super(DashBoard, self).__init__()
# setup UI
self.setupUi(self)
self.newOrderBtn.clicked.connect(self.newOrder)
self.soTab = []
Then, in the same class, I defined a method to handle the closeEvent and close all the dialogs.
def closeEvent(self, event):
for so in self.soTab:
if so:
so.close()
event.accept()
I couldn't understand the connectSlotsByName() method which is predominently used by pyuic4.. As far the class is single in a PyQt file it's ok since we can use self which will be associated with a single object throughout.. But when we try to use various classes from different files the problem and the need to use connectSlotsByName() arises.. Here's what i encountered which is weird..
I created a stacked widget..
I placed my first widget on it.. It
has a button called "Next >".
On clicking next it hides the current
widget and adds another widget which has the "click me" button..
The problem here is the click event for "click me" button in second is not captured.. It's a minimal example that i can give for my original problem.. Please help me..
This is file No.1..(which has the parent stacked widget and it's first page). On clicking next it adds the second page which has "clickme" button in file2..
from PyQt4 import QtCore, QtGui
import file2
class Ui_StackedWidget(QtGui.QStackedWidget):
def __init__(self,parent=None):
QtGui.QStackedWidget.__init__(self,parent)
self.setObjectName("self")
self.resize(484, 370)
self.setWindowTitle(QtGui.QApplication.translate("self", "stacked widget", None, QtGui.QApplication.UnicodeUTF8))
self.createWidget1()
def createWidget1(self):
self.page=QtGui.QWidget()
self.page.setObjectName("widget1")
self.pushButton=QtGui.QPushButton(self.page)
self.pushButton.setGeometry(QtCore.QRect(150, 230, 91, 31))
self.pushButton.setText(QtGui.QApplication.translate("self", "Next >", None, QtGui.QApplication.UnicodeUTF8))
self.addWidget(self.page)
QtCore.QMetaObject.connectSlotsByName(self.page)
QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL('clicked()'),self.showWidget2)
def showWidget2(self):
self.page.hide()
obj=file2.widget2()
obj.createWidget2(self)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = Ui_StackedWidget()
ui.show()
sys.exit(app.exec_())
Here's file2
from PyQt4 import QtGui,QtCore
class widget2():
def createWidget2(self,parent):
self.page = QtGui.QWidget()
self.page.setObjectName("page")
self.parent=parent
self.groupBox = QtGui.QGroupBox(self.page)
self.groupBox.setGeometry(QtCore.QRect(30, 20, 421, 311))
self.groupBox.setObjectName("groupBox")
self.groupBox.setTitle(QtGui.QApplication.translate("self", "TestGroupBox", None, QtGui.QApplication.UnicodeUTF8))
self.pushButton = QtGui.QPushButton(self.groupBox)
self.pushButton.setGeometry(QtCore.QRect(150, 120, 92, 28))
self.pushButton.setObjectName("pushButton")
self.pushButton.setText(QtGui.QApplication.translate("self", "Click Me", None, QtGui.QApplication.UnicodeUTF8))
self.parent.addWidget(self.page)
self.parent.setCurrentWidget(self.page)
QtCore.QMetaObject.connectSlotsByName(self.page)
QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL('clicked()'),self.printMessage)
def printMessage(self):
print("Hai")
Though in both the widgets(i mean pages)
QtCore.QMetaObject.connectSlotsByName(self.page)
the clicked signal in second dialog isn't getting processed. Thanks in advance.. Might be a beginner question..
A better question is "Why not just use new-style signals and slots?". They're much simpler and don't require any weird naming conventions:
from sys import argv, exit
from PyQt4 import QtCore, QtGui
class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self._layout = QtGui.QVBoxLayout()
self.setLayout(self._layout)
self._button = QtGui.QPushButton()
self._button.setText('Click NOW!')
self._layout.addWidget(self._button)
self._button.clicked.connect(self._printMessage)
#QtCore.pyqtSlot()
def _printMessage(self):
print("Hai")
if __name__ == "__main__":
app = QtGui.QApplication(argv)
main = MyWidget()
main.show()
exit(app.exec_())
At first, here is the minimal working example:
from sys import argv, exit
from PyQt4 import QtCore, QtGui
class widget2(QtGui.QWidget):
def __init__(self, args):
self.app = MainApp(args)
QtGui.QWidget.__init__(self)
self.setObjectName('I')
self._layout = QtGui.QVBoxLayout(self)
self.setLayout(self._layout)
self.pushButtoninWidget2 = QtGui.QPushButton(self)
self.pushButtoninWidget2.setObjectName("pushButtoninWidget2")
self.pushButtoninWidget2.setText('Click NOW!')
self._layout.addWidget(self.pushButtoninWidget2)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.pyqtSlot()
def on_pushButtoninWidget2_clicked(self):
print("Hai")
class MainApp(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
if __name__ == "__main__":
main = widget2(argv)
main.show()
exit(main.app.exec_())
When you trying to connect slots by name, you must give proper names to the slots and then someone (moc, uic, or you by calling connectSlotsByName) must connect them. Proper name for such a slot is: "on_PyQtObjectName_PyQtSignalName".
Note, that, if I'd omitted #QtCore.pyqtSlot() in the example, slot would be executed once for every appropriate overload (twice in this case).
You DO need to call connectSlotsByNames directly, cause there is no moc, which do it for you when you use QT in C++, and you do not use uic and .ui file. If you want to connect slots implicitly (I'm always doing so, except slots, connected directly in .ui), you'd better use more pytonish syntaxe: button.clicked.connect(self._mySlot).
And take a look at https://riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#connecting-slots-by-name
You do not need to call connectSlotsByName(), just remove those lines.
In file2, calling QtCore.QMetaObject.connectSlotsByName(self.page) tries to do this:
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL('clicked()'), self.on_pushButton_clicked())
That will not work for you since self.on_pushBotton_clicked() slot is not defined.
I find it is easiest to create your own connections in PyQt... I recommend removing the calls to connectSlotsByName from your both classes... you do not need it.
Also, your wdiget1 class should set the name of it's pushButton (preferably something other then "pushButton" to avoid confusion with the button in widget2).
Thank you so much jcoon for your reply.. But after a very long time banging my head against the wall i found the solution..
The problem was..
self.obj=test_reuse_stacked1.widget2()
self.obj.createWidget2(self)
instead of obj..
Here is #MarkVisser's QT4 code updated to QT5:
from sys import argv, exit
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget
class MyWidget(QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self._layout = QtWidgets.QVBoxLayout()
self.setLayout(self._layout)
self._button = QtWidgets.QPushButton()
self._button.setText('Click NOW!')
self._layout.addWidget(self._button)
self._button.clicked.connect(self._print_message)
#QtCore.pyqtSlot()
def _print_message(self):
print("Hai")
if __name__ == "__main__":
app = QApplication(argv)
main = MyWidget()
main.show()
exit(app.exec_())
Another minimal working example with Qt for Python aka PySide2/6.
Key ingredients:
widget to connect MUST have .setObjectName
function to connect MUST be decorated with #QtCore.Slot()
both objects (function AND widget) MUST be members of passed object (self here)
from PySide2 import QtCore, QtWidgets
# or from PySide6 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
layout = QtWidgets.QVBoxLayout(self)
self.button = QtWidgets.QPushButton(self)
self.button.setObjectName('button')
self.button.setText('Click Me!')
layout.addWidget(self.button)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.Slot()
def on_button_clicked(self):
print(f'Hai from {self.sender()}')
if __name__ == '__main__':
app = QtWidgets.QApplication([])
main = Widget()
main.show()
app.exec_()
I couldn't get mit any smaller really 🤔