How to set a hover event handler on a QPushButton in PyQt5 - pyqt5

I'm trying to set a hover event on a button to open an options menu implemented as a QDialog. Everything works so far except the hovering: the menu opens when the button is pressed and disappears if any of the options is selected or the mouse is moved away from the dialog.
Now I want to open the window without clicking the button but rather by hovering over it.
I've seen PyQt5 mouse hover functions and How to detect mouse hover event in PySide6 widget but i wasn't able to make it work this way.
My code looks like this:
class ElementWidget(QWidget):
def __init__ (self, p, element):
super().__init__(p)
layout = QHBoxLayout()
label = QLabel(element)
label.setFixedSize(200,39)
self.btn = QPushButton("btn")
self.btn.clicked.connect(self._openOptionsMenu)
self.btn.setFixedSize(50,39)
layout.addWidget(label)
layout.addWidget(self.btn)
self.setLayout(layout)
self.setFixedSize(250,60)
def _openOptionsMenu(self):
self.dlg = selfClosingDialog(self.closeOptionsMenu, parent = self)
self.dlg.setLayout(ElementOptionsLayout(self.closeOptionsMenu))
self.dlg.setWindowFlag(Qt.FramelessWindowHint)
self.dlg.setGeometry(QCursor.pos().x(), QCursor.pos().y() ,100,100)
self.dlg.show()
def closeOptionsMenu(self):
self.dlg.close()
if __name__ == "__main__":
app = QApplication([])
window = QMainWindow()
window.resize(500,400)
wid = ElementWidget(window,"Parabola_0")
window.show()
app.exec_()
with the custom dialog:
class selfClosingDialog(QDialog):
def __init__(self, closeFunc, parent=None):
super().__init__(parent)
self.closeFunc = closeFunc
def leaveEvent(self, event):
self.closeFunc()
The perfect solution would be to replace the clicked-event by some kind of an onHover

I found the answer.
It is not a signal but an event enterEvent that needs to be reimplemented by a subclass of QWidget
class HoverOpenBtn(QPushButton):
def __init__(self, text, openOtionsFunc, parent=None):
super().__init__(text, parent)
self.openFunc = openOtionsFunc
def enterEvent(self, event):
self.openFunc()

Related

PyQT5 & QtabWidget - changing values in different tabs

I've build a small PyQt5 application with a QtabWidget as my CentralWidget. Because I wanted to bring some structure into the code I create new tabs by using different classes. This works pretty fine so far.
class Main_window(QtWidgets.QMainWindow):
"""Main Window"""
def __init__(self, parent=None):
"""Initializer"""
super(Main_window, self).__init__(parent)
self.setGeometry(50, 50, 1100, 750)
# Create menu
self.qtMenu3()
self.tabWidget = QtWidgets.QTabWidget()
# sets the tabWidget as the central widget inside the QMainWindow
self.setCentralWidget(self.tabWidget)
self.tab_first = QtWidgets.QWidget()
self.tabWidget.addTab(FirstTab(), 'First')
self.tab_second = QtWidgets.QWidget()
self.tabWidget.addTab(SecondTab(), 'Second')
My SecondTab class looks like this and creates a GroupBox and two QTextEdits
class SecondTab(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("Groupbox")
layout = QGridLayout()
self.setLayout(layout)
groupbox = QGroupBox("GroupBox Example")
# groupbox.setCheckable(True)
layout.addWidget(groupbox)
# Layout manager QVBox (vertical)
vbox = QVBoxLayout()
groupbox.setLayout(vbox)
# radiobutton = QRadioButton("Radiobutton 1")
# vbox.addWidget(radiobutton)
textEdit_input = QTextEdit()
vbox.addWidget(textEdit_input)
textEdit_output = QTextEdit()
vbox.addWidget(textEdit_output)
The point where I struggle now is that I want to load a txt file and the text should update the empty textEdit_input in my second tab. Because the function should work for multiple tabs I don't want to attach it to my SecondTab class.
How can I properly address QTextEdit in my second tab?
Thanks for the input musicamante, changed the code according to your suggestions and it works.
self.tab_first = FirstTab()
self.tabWidget.addTab(self.tab_first,"Tab 1")
self.tab_second = SecondTab()
self.tabWidget.addTab(self.tab_second,"Tab 2")
and with self.tab_first.textEdit_input I can now access the needed fields.

QGraphicsPixmapItem is not being positioned correctly

I need to move a QGraphicsPixmapItem through a circle that it is at the top left corner of the image. That is, when I grab with the mouse the circle, I need the top left corner of the image to follow the circle. I subclassed a QGraphicsEllipseItem and reimplemented the itemChange method but when I set the position of the image to that value, the image is not being positioned correctly. What should I modify in my code?
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsView
from PyQt5 import QtGui, QtWidgets
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.scene = Scene()
self.view = QGraphicsView(self)
self.setGeometry(10, 30, 850, 600)
self.view.setGeometry(20, 22, 800, 550)
self.view.setScene(self.scene)
class Scene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(Scene, self).__init__(parent)
# other stuff here
self.set_image()
def set_image(self):
image = Image()
self.addItem(image)
image.set_pixmap()
class Image(QtWidgets.QGraphicsPixmapItem):
def __init__(self, parent=None):
super(Image, self).__init__(parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
def set_pixmap(self):
pixmap = QtGui.QPixmap("image.jpg")
self.setPixmap(pixmap)
self.pixmap_controller = PixmapController(self)
self.pixmap_controller.set_pixmap_controller()
self.pixmap_controller.setPos(self.boundingRect().topLeft())
self.pixmap_controller.setFlag(QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges, True)
def change_image_position(self, position):
self.setPos(position)
class PixmapController(QtWidgets.QGraphicsEllipseItem):
def __init__(self, pixmap):
super(PixmapController, self).__init__(parent=pixmap)
self.pixmap = pixmap
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
color = QtGui.QColor(0, 0, 0)
brush = QtGui.QBrush(color)
self.setBrush(brush)
def set_pixmap_controller(self):
self.setRect(-5, -5, 10, 10)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
self.pixmap.change_image_position(value)
return super(PixmapController, self).itemChange(change, value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
When a graphics item has a parent, its coordinate system is based on that parent, not on the scene.
The problem is that when you try to move the PixmapController, the movement is in parent coordinates (the pixmap item). When you check for the ItemPositionChange you are you're changing the parent position but the item position is changed anyway, based on the parent coordinate system.
While you could just return an empty QPoint (which will not change the item position), this wouldn't be a good choice: as soon as you release the mouse and start to move it again, the pixmap will reset its position.
The solution is not to set the movable item flag, but filter for mouse movements, compute a delta based on the click starting position, and use that delta to move the parent item based on its current position.
class PixmapController(QtWidgets.QGraphicsEllipseItem):
def __init__(self, pixmap):
super(PixmapController, self).__init__(parent=pixmap)
self.pixmap = pixmap
# the item should *NOT* move
# self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
color = QtGui.QColor(0, 0, 0)
brush = QtGui.QBrush(color)
self.setBrush(brush)
def set_pixmap_controller(self):
self.setRect(-5, -5, 10, 10)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.startPos = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
delta = event.pos() - self.startPos
self.parentItem().setPos(self.parentItem().pos() + delta)
If you want to use your change_image_position function, you need to change those functions accordingly; the code below does the same thing as the last line in the example above:
class Image(QtWidgets.QGraphicsPixmapItem):
# ...
def change_image_position(self, delta):
self.setPos(self.pos() + delta)
class PixmapController(QtWidgets.QGraphicsEllipseItem):
# ...
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
delta = event.pos() - self.startPos
self.pixmap.change_image_position(delta)
Tip: do not add a child widget to a QMainWindow like that, as it will not resize correctly when the window is resized. Use self.setCentralWidget(self.view) instead; if you want to add margins, use a container QWidget, set that widget as the central widget, add a simple QHBoxLayout (or QVBoxLayout), add the view to that layout and then set the margins with layout.setContentsMargins(left, top, right, bottom)

How to set QFrame color in an eventFilter?

I have a simple QWidget that contains a frame and two labels. I want to use eventFilter to change QFrame background color on label hover. Can someone please check the below code and tell me why I can't change the QFrame background and if it is the correct way for doing it?
import sys
from PyQt5.QtWidgets import QWidget, QHBoxLayout, \
QGraphicsDropShadowEffect, QPushButton, QApplication, QComboBox, QFrame, QLabel
from PyQt5 import QtCore
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout(self)
self.frame = QFrame(self)
self.setObjectName("frame")
self.frame_lay = QHBoxLayout()
self.one_label = QLabel(self.frame)
self.one_label.setText("one")
self.one_label.setObjectName("one")
self.two_label = QLabel(self.frame)
self.two_label.setText("two")
self.two_label.setObjectName("two")
self.one_label.installEventFilter(self)
self.two_label.installEventFilter(self)
self.frame_lay.addWidget(self.one_label)
self.frame_lay.addWidget(self.two_label)
self.frame.setStyleSheet("""QFrame{background-color: red;}""")
self.frame.setLayout(self.frame_lay)
self.frame_lay.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.frame)
self.setLayout(self.layout)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Enter:
if type(obj) == QLabel:
if obj.objectName() in ["one", "two"]:
print(obj.objectName())
self.frame.setStyleSheet("""QFrame#frame{background-color: blue;}""")
return super(QWidget, self).eventFilter(obj, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Should the installEventFilter be applied to QWidget or QFrame? The labels are contained within the QFrame.
Thanks
You set the frame object name for the "MainWindow", but in the event filter you used the object name for a QFrame class.
Just set the object name for the frame instead:
self.frame.setObjectName("frame")
Note that QLabel inherits from QFrame, so, using QFrame{background-color: red;} technically applies the background for both the parent frame and any child label.
In case you want to be more specific, you either use the object name as you did in the event filter, or use the .ClassName selector, which applies the sheet only to the class and not its subclasses (note the full stop character before QFrame):
self.frame.setStyleSheet(""".QFrame{background-color: red;}""")

How to set the order of widgets in QGridLayout

I am trying to put a QPushButton on top of a PlotWidget. My current code always has the plot widget on top of the button. How can I bring the button to the front of the QGridLayout?
Or is there a better way in PyQt5 to overlay widgets?
I have tried using .raise__() and .lower__() neither worked as expected.
As far as I found there is no way in PyQt5 to set a z value
Changing the order of adding the widgets to the QGridLayout had no effect
The PlotWindow class is used in a Stacked widget in a Window controller class as well as some other classes
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.central_widget = QtWidgets.QStackedWidget()
self.setCentralWidget(self.central_widget)
self.plot = PlotWindow(self)
self.central_widget.addWidget(self.plot)
self.central_widget.setCurrentWidget(self.plot)
self.show()
class PlotWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(PlotWindow, self).__init__(parent)
plot_frame = pyqtgraph.PlotWidget()
self.connect_btn = QtWidgets.QPushButton("Connect", self)
plot_wrapper_layout = QtWidgets.QGridLayout()
plot_wrapper_layout.addWidget(plot_frame, 0, 0, 12, 12)
plot_wrapper_layout.addWidget(self.connect_btn, 11, 11, 1, 1)
self.setLayout(plot_wrapper_layout)
Expecting: Button to be visible on top of the graph in the bottom right corner
Result: The connect button is hidden behind the plot widget
plot_frame = pyqtgraph.PlotWidget(self). That puts the graph widget before the push button in PlotWindow.children() which changes the order in which they are rendered.
Heikie's comment fixed the issue

Matplot lib does not plot with PyQt

I'm having a problem with PyQt and Mathplotlib.
Here you can find a pseudocode of what I am doing: I have a class "MainWindow" that creates a main window with a menu and an empty mathplotlib graph. When I click on the menu Item, the method "Select" is executed that opens a new Dialog. There is also a method that plots on the grah the content of the global variable Data.
import TeraGui
Data = []
class MainWindow(QMainWindow, TeraGui.Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.actionSelect.triggered.connect(self.Select)
# Create the frame with the graphs
self.create_main_frame()
#Plot empty graphs
self.axes.clear()
self.canvas.draw()
def create_main_frame(self):
self.main_frame = QWidget()
# Create the mpl Figure and FigCanvas objects.
# 5x4 inches, 100 dots-per-inch
#
self.dpi = 100
self.fig = Figure((5.0, 4.0), dpi=self.dpi)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
#
self.axes = self.fig.add_subplot(111)
# Create the navigation toolbar, tied to the canvas
#
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
#
# Layout with box sizers
#
vbox = QVBoxLayout()
vbox.addWidget(self.canvas)
vbox.addWidget(self.mpl_toolbar)
self.main_frame.setLayout(vbox)
self.setCentralWidget(self.main_frame)
def Plotting(self):
""" Redraws the figure
"""
print "I am here"
time = Data[0]
sig = Data[]
plot(time, sig)
# clear the axes and redraw the plot anew
#
self.axes.clear()
self.axes.plot(time, sig)
self.canvas.draw()
def Select(self):
dialog = Dialog(self)
dialog.exec_()
Now, if I add in the init method of the MainWindow class these lines:
Global Data
Data = [[1,2,3],[4,5,6]]
self.Plotting()
"I am here" is printed and the plot is correctly displayed into the graph, BUT if I don't add these lines and i try to call Plotting from the Dialog class it doesn't work. "I am here" is plotted but the plot stays empty. In the Dialog class, method "accept" is caled when the "ok" button of a button box is pressed:
class Dialog(QDialog, TeraGui.Ui_SelectFiles):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setupUi(self)
def accept(self):
global Data
Data = [[1,2,3],[4,5,6]]
MainWindow().Plotting()
The Plotting method draws also a separate plot by means of the command "plot(time,sig)". This plot is always showed correctly regardless the way used to call Plotting.
These are my fist tries with PyQt and matplotlib and I am not able to identify the mistake.
The problem is with the line
MainWindow().Plotting()
When you write MainWindow() you are actually creating a new instance of the MainWindow class and calling its Plotting() function, not the one of your existing MainWindow instance. This window is never shown, and since you don't save a reference to it, is subsequently deleted when accept() returns. The only evidence of its existence is the 'i am here' message it writes to the console. This is why you don't see the plot.
In this case, you are setting your MainWindow instance as the parent of dialog through dialog = Dialog(self), so you could access it though a call to parent().
self.parent().Plotting()
You should also consider adding another parameter to the Plotting() function so you can pass your data directly to it instead of having to declare globals everywhere.
def Plotting(self, Data):
...