Hover Event for a QGraphicsItem (PyQt4) - python

I want some small text to pop up when I have my curser over a QGraphicsItem in my QGraphicsScene. I have a class that inherits from QGraphicsItem, and this represents my graphical items in the scene.
I tried using the QGraphicsItem.hoverEnterEvent and I also set the setAcceptHoverEvents(True), but I still can't enable that hover event. I also came across an event filter method but I'm not sure where to implement it.
Should I install the event filter in the QGraphicsItem class, or the scene? I tried both and I'm still not getting the desired result. I want to be able to hover over all the items in the scene.
UPDATE:
So I tried doing this but the hover event still isn't being detected.
class graphics_Object(QtGui.QGraphicsPixmapItem):
def __init__(self, parent=None):
super(graphics_Object, self).__init__(parent)
pixmap = QtGui.QPixmap("item.png")
self.graphics_pixItem = QtGui.QGraphicsPixmapItem(pixmap.scaled(40, 40, QtCore.Qt.KeepAspectRatio))
self.graphics_pixItem.setFlag(QtGui.QGraphicsPixmapItem.ItemIsSelectable)
self.graphics_pixItem.setFlag(QtGui.QGraphicsPixmapItem.ItemIsMovable)
self.graphics_pixItem.setAcceptHoverEvents(True)
def hoverEnterEvent(self, event):
print 'hello'
class graphicsScene(QtGui.QGraphicsScene):
def __init__(self, parent=None):
super(graphicsScene, self).__init__(parent)
def mousePressEvent(self, event):
self.graphics_item = graphics_Object()
def mouseReleaseEvent(self, event)
self.addItem(self.graphics_item.graphics_pixItem)
self.graphics_item.graphics_pixItem.setPos(event.scenePos())
class Form(QtGui.QMainWindow):
def __init__(self):
super(Form, self).__init__()
self.ui = uic.loadUi('form.ui')
self.scene = graphicsScene()
self.ui.view.setScene(self.scene)
self.setMouseTracking(True)

You are making another pixmapItem inside you graphics_Object class, graphics_Object is already a subclass of graphicsPixmapItem so I don't see a purpose for this.
Then you only add that nested pixmapItem to the scene and your hoverEnterEvent is on the graphics_Object which is never added to the scene. That is why you are not receiving hover events.
One of many solutions would be to just add your graphics object to the scene instead of the nested graphicsPixmapItem and use setPixmap() in the init of graphics_Object
class graphics_Object(QtGui.QGraphicsPixmapItem):
def __init__(self, parent=None):
super(graphics_Object, self).__init__(parent)
pixmap = QtGui.QPixmap("item.png")
self.setPixmap(pixmap.scaled(40, 40, QtCore.Qt.KeepAspectRatio))
self.setFlag(QtGui.QGraphicsPixmapItem.ItemIsSelectable)
self.setFlag(QtGui.QGraphicsPixmapItem.ItemIsMovable)
self.setAcceptHoverEvents(True)
def hoverEnterEvent(self, event):
print('hello')
class graphicsScene(QtGui.QGraphicsScene):
def __init__(self, parent=None):
super(graphicsScene, self).__init__(parent)
def mousePressEvent(self, event):
self.graphics_item = graphics_Object()
def mouseReleaseEvent(self, event):
print('adding to scene')
self.addItem(self.graphics_item)
self.graphics_item.setPos(event.scenePos())
When you inherit (subclass) from a pyqt class or any class in python, think of the new class as a "copy" of the inherited class. It will behave the exact same way as the base class until you overwrite a method, in this case, we override the "init" and "hoverEnterEvent" methods to do our custom stuff. Everything else about a QGraphicsPixmapItem stays the same

Related

class same signal connected to multiple instances

If I define a disable_signal = QtCore.pyqtSignal() outside of the custom button class, the behaviour I am looking for (all instance are disabled when clicked upon).
class CustomButton(QtWidgets.QToolButton):
def __init__(self, parent, disable_signal):
super(CustomButton, self).__init__(parent)
self.disable_signal = disable_signal
self.disable_signal.connect(self.disable)
self.pressed.connect(self.buttonPressed)
def buttonPressed(self):
self.disable_signal.emit()
#QtCore.pyqtSlot()
def disable(self):
print("received")
self.setEnabled(False)
However, if I define the signal as a class attribute, each instance behave as if they each had their individual signal (pressing upon one, disable only that one):
class CustomButton(QtWidgets.QToolButton):
disable_signal = QtCore.pyqtSignal()
def __init__(self, parent):
super(CustomButton, self).__init__(parent)
self.disable_signal.connect(self.disable)
self.pressed.connect(self.buttonPressed)
def buttonPressed(self):
self.disable_signal.emit()
#QtCore.pyqtSlot()
def disable(self):
print("received")
self.setEnabled(False)
I don't understand why the signal is not shared? I tried to use instead of self.disable_signal.connect(self.disable), CustomButton.disable_signal.connect(self.disable) but I get the error: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'.

Use same sublcass override method for QTableWidget and QListWidget

I have a QTableWidget and QListWidget that I want to subclass with similar override methods like mousePressEvent(). In an effort to keep my code dry, I would prefer not to have two unique subclasses that have duplicate logic. Can anyone suggest a pattern to use in this case?
Pseudo code:
from PySide2 import QtWidgets, QtCore
class ChildClass(QtWidgets.QListWidget): # Could be QTableWidget
_main_window = None
_context_menu = None
def __init__(self, main_window):
super().__init__(main_window)
self._main_window = main_window
self.setDragEnabled(True)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_context_menu)
def mousePressEvent(self, event):
super().mousePressEvent(event)
self._main_window.set_active_browser(self.parent())
You can use a mixin:
class MousePressOverride:
def mousePressEvent(self, event):
super().mousePressEvent(event)
self._main_window.set_active_browser(self.parent())
class ChildClass(MousePressOverride, QtWidgets.QListWidget):
# ...
Note that the order of the multiple inheritance is very important, as the mousePressEvent of MousePressOverride has to override that of the QListWidget.
Please consider that if you want to interact/communicate with a parent object, you should consider using signals instead, as that would better comply with the OOP pattern:
class MousePressOverride:
someSignal = QtCore.pyqtSignal(object)
def mousePressEvent(self, event):
super().mousePressEvent(event)
self.someSignal.emit(self)
class ChildClass(MousePressOverride, QtWidgets.QListWidget):
def __init__(self):
super().__init__()
# ...
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.view = ChildClass()
self.view.someSignal.connect(self.set_active_browser)
def set_active_browser(self, obj):
# ...

Widget dragging not working when hovering over the widget itself

I'm having an issue where I have implemented a dragging feature for widgets using eventFilter(), but it seems when I drag towards the right and my cursor hovers over the widget which in my case is a QPushButton it seems to stop tracking until I hover out of the widget
How would I fix this?
class widget1(QWidget):
def __init__(self):
super().__init__()
self.widget = QPushButton("button0", self)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.createbutton = QPushButton('+', self)
self.createbutton.setGeometry(5, 5, 15, 15)
self.createbutton.clicked.connect(self.createWidget)
self.show()
def createWidget(self):
self.new_widget = widget1()
self.new_widget.setParent(self)
self.new_widget.show()
self.new_widget.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.MouseMove:
MousePos = QPoint(event.pos())
if event.buttons() == Qt.LeftButton:
source.move(source.x() + MousePos.x(), source.y() + MousePos.y())
return super(MainWindow, self).eventFilter(source, event)
Basically I have a class with a widget in it called widget1() (This will be my custom widget in the future) I am then adding it dynamically to the window every time I press self.createbutton using .show() instead of using a layout, then it installs the event filter to it so that it can allow for dragging.
The event filter is receiving events for the widget1 instance but not for its child widgets (i.e. the QPushButton). A quick fix is to invoke QMouseEvent.ignore for the QPushButton's mouseMoveEvent so the mouse event will be propagated up to the parent widget and received in the event filter.
class widget1(QWidget):
def __init__(self):
super().__init__()
self.widget = QPushButton("button0", self)
self.widget.mouseMoveEvent = lambda event: event.ignore()

Closing a QDialog and disconnect signals fails

I need to open a QDialog in a different class of the QMainWindow, and after closing the QDialog, all the signals must be disconnected.
I can open the QDialog by pressing a combination of keys and then, when it is open instantly is connected to the button_pressedmethod which itself is connected to self.spanSelector_press and self.spanSelector_
This is the code so far:
class Window(QMainWindow): #This is a matplotlib figure
def __init__(self):
QMainWindow.__init__(self)
#A lot of stuff for the matplotlib figure
def button_pressed(self):
self.select_data = SelectData(self)
self.select_data.show()
self.cid_press = self.figure_canvas.mpl_connect('button_press_event',
self.spanSelector_press)
self.cid_release = self.figure_canvas.mpl_connect('button_release_event',
self.spanSelector_release)
def spanSelector_press(self, event):
if event.button ==1:
self.limite = "minimum"
self.clearMarker() #This is another method to erase the previous line drawn
self.marker = self.axes.axvline(Window.minimumCoords, linestyle='dashed',
linewidth = 2, color = "green")
self.figure_canvas.draw_idle()
Window.initial_marker = self.marker
self.xmin = event.xdata
def spanSelector_release(self, event):
pass
class SelectData(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent, Qt.WindowStaysOnTopHint)
def closeEvent(self, event):
#I need to disconnect all the signals when i close the QDialog
view = self.parent()
view.figure_canvas.mpl_disconnect(view.cid_press)
view.figure_canvas.mpl_disconnect(view.cid_release)
view.deselect()
event.accept()
How can i disconnect all the signals in the button_pressed method after closing the QDialog? Hope you can help me.
According to the matplotlib docs you can disconnect using the connection id, which you'll need to save by doing something like this in Window:
def button_pressed(self):
self.select_data = SelectData(self)
self.select_data.show()
self.cid_press = self.figure_canvas.mpl_connect('button_press_event', self.spanSelector_press)
self.cid_release = self.figure_canvas.mpl_connect('button_release_event', self.spanSelector_release)
Your SelectData class has a reference to its parent class (Window) that can be had by calling parent(), so you can use that to do the disconnection.
def closeEvent(self, event):
window = self.parent()
window.figure_canvas.mpl_disconnect(window.cid_press)
window.figure_canvas.mpl_disconnect(window.cid_release)
event.accept()
I haven't tested that, but it should be pretty close.
Edit: be sure to pass parent to QDialog constructor as such:
class SelectData(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent, Qt.WindowStaysOnTopHint)

PySide multi-window, get QStackWidget to work

I need to create multi-window GUI, first I tried it with QWidgets, but finally I discover QStackWidget tool I need to use. So Im trying to, but Ive got some problems. Thanks for Your time.
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.mainWidget = MainWidget()
self.searchWidget = SearchWidget()
self.sWidget = QStackedWidget()
self.sWidget.addWidget(self.mainWidget)
self.sWidget.addWidget(self.searchWidget)
self.initUI()
and calling setCurrentWidget from the sub_widget class:
class MainWidget(QWidget):
def __init__(self, parent=MainWindow):
super(MainWidget,self).__init__()
self.initUI()
def initUI(self):
searchButton = QPushButton('searchButton',self)
optionButton = QPushButton('optionButton',self)
quitButton = QPushButton('quitButton',self)
listButton = QPushButton('listButton',self)
searchButton.clicked.connect(self.goSearch)
hbox = QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addWidget(searchButton)
vbox.addWidget(optionButton)
vbox.addLayout(hbox)
self.setLayout(vbox)
def goSearch(self):
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
Ive got this message from IDE:
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
AttributeError: 'PySide.QtGui.QStackedWidget' object has no attribute 'sWidget'
What is the thing Im doing wrong?
I'm going to comment on the code you posted here: http://pastebin.com/fBfS1X5m
An important thing to know is that you can put widgets within widgets and so on. For example:
class Widget(QWidget):
def __init__(self, parent=None):
layout = QVBoxLayout(self)
childWidget = QWidget(parent=self)
layout.addWidget(childWidget)
Just a quick note: You don't need setLayout if you pass self to the main layout constructor - via the docs.
Anyways, what I'm trying to illustrate here is that the QStackedWidget and the SearchWidget really shouldn't be a part of the MainWindow, but should live inside their own relevant widget that will handle switching between the QStackedWidget pages.
For example the MainWindow.__init__ would only look like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.mainWidget = MainWidget(self)
self.setCentralWidget(self.mainWidget)
self.initUI()
Your MainWidget would then look something like:
class MainWidget(QtGui.QWidget):
...
def initUI(self):
...
self.stack = QtGui.QStackedWidget(parent=self)
self.searchWidget = SearchWidget(parent=self)
self.searchWidget.searchButton.clicked.connect(self.goSearch)
self.backWidget = BackWidget(parent=self)
self.backWidget.backButton.clicked.connect(self.goBack)
...
def goSearch(self):
self.stack.setCurrentWidget(self.backWidget)
def goBack(self):
self.stack.setCurrentWidget(self.searchWidget)
I've renamed some of the class names to make a little more sense (To me at least). SearchWidget was your old MainWidget. BackWidget was your old SearchWidget. Following those changes SearchWidget would look the same as your old MainWidget with one exception - we save a reference to the search button so we can access it in the MainWidget class as seen above (when we connect their signals to our slots). We do the same for the button in BackWidget.
The two renamed "child" widgets:
class SearchWidget(QtGui.QWidget):
...
def initUI(self):
self.searchButton = QtGui.QPushButton('searchButton', parent=self)
optionButton = QtGui.QPushButton('optionButton', parent=self)
quitButton = QtGui.QPushButton('quitButton', parent=self)
listButton = QtGui.QPushButton('listButton', parent=self)
vbox = QtGui.QVBoxLayout(self)
vbox.addStretch(1)
vbox.addWidget(self.searchButton)
vbox.addWidget(optionButton)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox.addLayout(hbox)
class BackWidget(QtGui.QWidget):
...
def initUI(self):
self.backButton = QtGui.QPushButton('GoBack', parent=self)
So now we have something like:
MainWindow
|---MainWidget
|---QStackedWidget
|---SearchWidget
|---BackWidget
You can find the full working code here.
This line:
def __init__(self, parent=MainWindow):
sets the MainWindow class as a default argument, when you actually need an instance. But even if it was an instance, in the next line, you also fail to pass it on to the base-class:
super(MainWidget,self).__init__()
What you need to do instead is something like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
# pass an instance of MainWindow here
self.mainWidget = MainWidget(self)
...
class MainWidget(QWidget):
def __init__(self, parent):
# pass the parent to the base-class
super(MainWidget, self).__init__(parent)
...
UPDATE:
The stack-widget will re-parent any widgets added to it, so that it becomes the parent. There are ways of working around this, but I think the real problem with your code is that you have the structure backwards. The buttons that set the current widget should be controlled by the main-window, and the widgets in the stack should work completely independantly of that.

Categories