PyQt5 A QA about menubar visibility by mouseover - python

Im using PyQT5 and Python Version is 3.78.
I got a request than when I mouse over the button and display menubar.I made that code and it works.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAction, qApp, QInputDialog, QLineEdit)
from app.strategy.menu_strategy import MenuStrategy
class DrawMenuWnd(QMainWindow):
def __init__(self, title, parent=None):
super(DrawMenuWnd, self).__init__(parent)
self.ctrl = None # source from wnd hook event
# self.menu_stg_obj = MenuStrategy(state_mq) # stop operation when input sth..
# set title
self.setWindowTitle(title)
# set win size and attrs
self.setGeometry(100, 100, 100, 100)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool | Qt.WindowStaysOnTopHint)
self.initial_ui()
def initial_ui(self):
self.menubar = self.menuBar()
self.menubar.setFont(QFont('微软雅黑'))
self.coin = QIcon(r'./arrow.jpeg')
# root menu
self.root_menu = self.menubar.addMenu(self.coin, "")
# mouse child menu
self.left_single = QAction("left click", self)
self.left_double_single = QAction("left double click", self)
self.right_single = QAction("right click", self)
self.root_menu.addAction(self.left_single)
self.root_menu.addAction(self.left_double_single)
self.root_menu.addAction(self.right_single)
# seperator
self.root_menu.addSeparator()
# keyboard child menu
self.input = QAction("keyboard input", self)
self.root_menu.addAction(self.input)
# connect function
# self.left_single.triggered.connect(self.menu_stg_obj.click)
# self.left_double_single.triggered.connect(self.menu_stg_obj.double_click)
# self.right_single.triggered.connect(self.menu_stg_obj.right_click)
# self.input.triggered.connect(self.menu_stg_obj.keyboard_input)
# set event filter
self.menubar.installEventFilter(self)
#classmethod
def initial_menu(cls, title):
app = QtWidgets.QApplication(sys.argv)
window = DrawMenuWnd(title)
window.show()
app.exec_()
def eventFilter(self, object, event):
try:
if event.type() == QEvent.Enter:
print("Mouse is over the label")
if self.root_menu.isHidden():
self.root_menu.show()
# print('program stop is', self.stop)
return True
elif event.type() == QEvent.Leave:
print("Mouse is not over the label")
self.root_menu.hide()
# print('program stop is', self.stop)
return False
except Exception as error:
print(error)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = DrawMenuWnd("")
window.show()
sys.exit(app.exec())
Now I got a question that when I mouse away from button, the menubar should disappear but now not.
So now I wanna let the menubar disappear after my mouse away from the button, I try to hide the menubar in using function hide() but it not works
Anybody help?
thanks

What I meant is something like that :
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAction, qApp, QInputDialog, QLineEdit,QMenu)
class MyQMenu(QMenu):
def __init__(self, *args, **kwargs):
QMenu.__init__(self, *args, **kwargs)
def leaveEvent(self, QEvent):
# here the code for mouse leave
self.close()
print('Leave')
class DrawMenuWnd(QMainWindow):
def __init__(self, title, parent=None):
super(DrawMenuWnd, self).__init__(parent)
self.ctrl = None # source from wnd hook event
# self.menu_stg_obj = MenuStrategy(state_mq) # stop operation when input sth..
# set title
self.setWindowTitle(title)
# set win size and attrs
self.setGeometry(100, 100, 100, 100)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool | Qt.WindowStaysOnTopHint)
self.initial_ui()
def initial_ui(self):
self.menubar = self.menuBar()
self.menubar.setFont(QFont('微软雅黑'))
self.coin = QIcon(r'./arrow.jpeg')
# root menu
self.root_menu = MyQMenu( "")
self.root_menu.setIcon(self.coin)
self.menubar.addMenu(self.root_menu)
# mouse child menu
self.left_single = QAction("left click", self)
self.left_double_single = QAction("left double click", self)
self.right_single = QAction("right click", self)
self.root_menu.addAction(self.left_single)
self.root_menu.addAction(self.left_double_single)
self.root_menu.addAction(self.right_single)
# seperator
self.root_menu.addSeparator()
# keyboard child menu
self.input = QAction("keyboard input", self)
self.root_menu.addAction(self.input)
# connect function
# self.left_single.triggered.connect(self.menu_stg_obj.click)
# self.left_double_single.triggered.connect(self.menu_stg_obj.double_click)
# self.right_single.triggered.connect(self.menu_stg_obj.right_click)
# self.input.triggered.connect(self.menu_stg_obj.keyboard_input)
# set event filter
self.menubar.installEventFilter(self)
#classmethod
def initial_menu(cls, title):
app = QtWidgets.QApplication(sys.argv)
window = DrawMenuWnd(title)
window.show()
app.exec_()
def eventFilter(self, object, event):
try:
if event.type() == QEvent.Enter:
print("Mouse is over the label")
if self.root_menu.isHidden():
self.root_menu.show()
# print('program stop is', self.stop)
return True
elif event.type() == QEvent.Leave:
print("Mouse is not over the label")
self.root_menu.hide()
# print('program stop is', self.stop)
return False
except Exception as error:
print(error)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = DrawMenuWnd("")
window.show()
sys.exit(app.exec())

Related

Adding animation to QPushbutton enterEvent and exitEvent

I'm trying to add custom animation to QPushbutton without making a custom QPushbutton and overriding its enterEvent() and leaveEvent().
So far I've tried this,
#staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
'''
Method to:
=> Add hover animation for provided button
'''
enterShift = QPropertyAnimation(button,b'pos',button)
exitShift = QPropertyAnimation(button,b'pos',button)
def enterEvent(e):
pos=button.pos()
enterShift.setStartValue(pos)
enterShift.setEndValue(QPoint(pos.x()+3,pos.y()+3))
enterShift.setDuration(100)
enterShift.start()
Effects.dropShadow(button,1,2)
def leaveEvent(e):
pos=button.pos()
exitShift.setStartValue(pos)
exitShift.setEndValue(QPoint(pos.x()-3,pos.y()-3))
exitShift.setDuration(100)
exitShift.start()
Effects.dropShadow(button)
button.enterEvent=enterEvent
button.leaveEvent=leaveEvent
But when I move the mouse very quickly in and out of the button before the animation finishes, The button starts to move wierdly towards the North-West direction.
Button Animation Using Dynamic Positions
I figured out this was due to the leaveEvent() being triggered before enterEvent() even finishes and also because the start and end values are dynamic. So, I tried providing currentPos as a static position and using it instead,
#staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
'''
Method to:
=> Add hover animation for provided button
'''
enterShift = QPropertyAnimation(button,b'pos',button)
enterShift.setStartValue(currentPos)
enterShift.setEndValue(QPoint(currentPos.x()+3,currentPos.y()+3))
enterShift.setDuration(100)
exitShift = QPropertyAnimation(button,b'pos',button)
exitShift.setStartValue(QPoint(currentPos.x()-3,currentPos.y()-3))
exitShift.setEndValue(currentPos)
exitShift.setDuration(100)
def enterEvent(e):
button.setProperty(b'pos',exitShift.endValue())
enterShift.start()
Effects.dropShadow(button,1,2)
def leaveEvent(e):
exitShift.start()
Effects.dropShadow(button)
button.enterEvent=enterEvent
button.leaveEvent=leaveEvent
On running, as soon as the mouse enters the QPushbutton, it moves to the top-left of its parent widget and the animation starts working fine. I can't figure out why this is happening. But I was able to get that, it only happened when I used any static value in the animation.
Button Animation with Static Position:
Here is an example:
import sys
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
# This is the same method mentioned above
from styling import addButtonHoverAnimation
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout=QVBoxLayout()
button1 = QPushButton("Proceed1", self)
layout.addWidget(button1)
button2 = QPushButton("Proceed2", self)
layout.addWidget(button2)
self.setLayout(layout)
self.resize(640, 480)
addButtonHoverAnimation(button1)
addButtonHoverAnimation(button2)
def main():
app = QApplication(sys.argv)
view = Widget()
view.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
The problem is that probably when the state is changed from enter to leave (or vice versa) the previous animation still does not end so the position of the widget is not the initial or final position, so when starting the new animation there is a deviation that accumulates. One possible solution is to initialize the position and keep it as a reference.
On the other hand you should not do x.fooMethod = foo_callable since many can fail, in this case it is better to use an eventfilter.
import sys
from dataclasses import dataclass
from functools import cached_property
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
#dataclass
class AnimationManager(QObject):
widget: QWidget
delta: QPoint = QPoint(3, 3)
duration: int = 100
def __post_init__(self):
super().__init__(self.widget)
self._start_value = QPoint()
self._end_value = QPoint()
self.widget.installEventFilter(self)
self.animation.setTargetObject(self.widget)
self.animation.setPropertyName(b"pos")
self.reset()
def reset(self):
self._start_value = self.widget.pos()
self._end_value = self._start_value + self.delta
self.animation.setDuration(self.duration)
#cached_property
def animation(self):
return QPropertyAnimation(self)
def eventFilter(self, obj, event):
if obj is self.widget:
if event.type() == QEvent.Enter:
self.start_enter_animation()
elif event.type() == QEvent.Leave:
self.start_leave_animation()
return super().eventFilter(obj, event)
def start_enter_animation(self):
self.animation.stop()
self.animation.setStartValue(self.widget.pos())
self.animation.setEndValue(self._end_value)
self.animation.start()
def start_leave_animation(self):
self.animation.stop()
self.animation.setStartValue(self.widget.pos())
self.animation.setEndValue(self._start_value)
self.animation.start()
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button1 = QPushButton("Proceed1", self)
button1.move(100, 100)
button2 = QPushButton("Proceed2", self)
button2.move(200, 200)
self.resize(640, 480)
animation_manager1 = AnimationManager(widget=button1)
animation_manager2 = AnimationManager(widget=button2)
def main():
app = QApplication(sys.argv)
view = Widget()
view.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
184 / 5000
Resultados de traducción
If you are using a layout then you must reset the position since the layout does not apply the position change immediately but only when the parent widget applies the changes.
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button1 = QPushButton("Proceed1")
button2 = QPushButton("Proceed2")
lay = QVBoxLayout(self)
lay.addWidget(button1)
lay.addWidget(button2)
self.resize(640, 480)
self.animation_manager1 = AnimationManager(widget=button1)
self.animation_manager2 = AnimationManager(widget=button2)
def resizeEvent(self, event):
super().resizeEvent(event)
self.animation_manager1.reset()
self.animation_manager2.reset()

Getting mouse position doesn't work if button is clicked

I'm trying to get mouse position(in-window) whenever the left mouse button is pressed using:
def mousePressEvent(self, event):
print(event.pos())
however, any kind of interactive widget overrides it, for example- clicking a button doesn't print mouse position, same with line edit and others(maybe except label). How would I get the mouse position even if those widgets are clicked?
Explanation:
In the case of QWidgets, you have to take into account:
The transmission of the mouse event is from the top widget(child) to the bottom widget(parent).
If an element consumes the mouse event (ie use event.accept()) then that event will no longer be transmitted.
Solution:
If you want to detect the mouse event on a window then you could monitor that event on the window itself (QWindow) through an eventfilter:
import sys
from PyQt5.QtCore import pyqtSignal, QEvent, QObject, QPoint
from PyQt5.QtWidgets import (
QApplication,
QLineEdit,
QPushButton,
QTextEdit,
QVBoxLayout,
QWidget,
)
class MouseHelper(QObject):
pressed = pyqtSignal(QPoint)
released = pyqtSignal(QPoint)
def __init__(self, window):
super().__init__(window)
self._window = window
self.window.installEventFilter(self)
#property
def window(self):
return self._window
def eventFilter(self, obj, event):
if self.window is obj:
if event.type() == QEvent.MouseButtonPress:
self.pressed.emit(event.pos())
elif event.type() == QEvent.MouseButtonRelease:
self.released.emit(event.pos())
return super().eventFilter(obj, event)
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button = QPushButton("Press me")
lineedit = QLineEdit()
textedit = QTextEdit()
lay = QVBoxLayout(self)
lay.addWidget(button)
lay.addWidget(lineedit)
lay.addWidget(textedit)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Widget()
w.show()
helper = MouseHelper(w.windowHandle())
helper.pressed.connect(lambda point: print("pressed:", point))
helper.released.connect(lambda point: print("released:", point))
sys.exit(app.exec_())

What is the signal for user clicks the down arrow on the QComboBox?

I need to execute a method whenever the user clicks the down arrow on the combo box. I've tried the signals listed in the documentations but none of the worked.
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.combo = QComboBox(self)
self.combo.signal.connect(self.mymethod)
self.show()
def mymethod(self):
print('hello world')
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
There is no signal that is emitted when the down arrow is pressed but you can create override the mousePressEvent method and verify that this element was pressed:
import sys
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import (
QApplication,
QComboBox,
QStyle,
QStyleOptionComboBox,
QVBoxLayout,
QWidget,
)
class ComboBox(QComboBox):
arrowClicked = pyqtSignal()
def mousePressEvent(self, event):
super().mousePressEvent(event)
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
sc = self.style().hitTestComplexControl(
QStyle.CC_ComboBox, opt, event.pos(), self
)
if sc == QStyle.SC_ComboBoxArrow:
self.arrowClicked.emit()
class Window(QWidget):
def __init__(self):
super().__init__()
self.combo = ComboBox()
self.combo.arrowClicked.connect(self.mymethod)
lay = QVBoxLayout(self)
lay.addWidget(self.combo)
lay.setAlignment(Qt.AlignTop)
def mymethod(self):
print("hello world")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())

How do I get a signal from a QWiget to change get object?

Hello I have a QWidget and If I click on it, I want to get the object (the QWidget Element I clicked on) is there anyway to do that?
I already found some code but I only get the MouseClickEvent from
self.widget_34.mouseReleaseEvent = lambda event: self.myfunction(event)
Although the solution offered by #Cin is interesting, it has a serious problem: it cancels the mousePressEvent of the widget, so the widget loses the behavior it could have when the widget is pressed, for example the button no longer emits the clicked signal, other widget also They will have the same problem.
A less intrusive solution is to use eventFilter:
import sys
import weakref
from PyQt5 import QtCore, QtWidgets
class ClickListener(QtCore.QObject):
clicked = QtCore.pyqtSignal(QtWidgets.QWidget)
def addWidget(self, widget, other_widget=None):
if not hasattr(self, "_widgets"):
self._widgets = {}
widget.installEventFilter(self)
self._widgets[widget] = widget if other_widget is None else other_widget
weakref.ref(widget, self.removeWidget)
def eventFilter(self, obj, event):
if (
obj in self._widgets
and event.type() == QtCore.QEvent.MouseButtonPress
):
self.clicked.emit(self._widgets[obj])
return super(ClickListener, self).eventFilter(obj, event)
def removeWidget(self, widget):
if hasattr(self, "_widgets"):
if widget in self._widgets:
del self._widgets[widget]
class App(QtWidgets.QWidget):
def __init__(self):
super().__init__()
button = QtWidgets.QPushButton("Press Me")
label = QtWidgets.QLabel("Stack Overflow")
spinBox = QtWidgets.QSpinBox()
te = QtWidgets.QTextEdit()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button)
lay.addWidget(label)
lay.addWidget(spinBox)
lay.addWidget(te)
listener = ClickListener(self)
listener.clicked.connect(self.onClicked)
listener.addWidget(button)
listener.addWidget(label)
listener.addWidget(spinBox.lineEdit(), spinBox)
listener.addWidget(te.viewport(), te)
def onClicked(self, obj):
print("Clicked, from", obj)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
I am not sure this will be a proper solution or not but I think, you can use the partial method of functools module. A collable object can be treated as a function for the purposes of this module. Here you can see my example,
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
import functools
class App(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(200,200,200,200)
self.button = QPushButton('Button', self)
self.button.move(50,50)
self.label = QLabel(self)
self.label.setText("Label")
self.label.move(100,100)
self.items = [self.button, self.label]
for i in self.items:
i.mousePressEvent = functools.partial(self.getClickedItem, source_object=i)
self.show()
def getClickedItem(self, event, source_object=None):
print("Clicked, from", source_object)
#item text
#print(source_object.text())
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

error when close with effect fade window pyqt5

I have a bit when try change new window UI with effect fade. I added effect on closeEvent of mainwindow but it doen't work.
This is my code:
library used:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import uic
load ui
uifile_1 = 'home.ui'
form_1, base_1 = uic.loadUiType(uifile_1)
uifile_2 = 'plate.ui'
form_2, base_2 = uic.loadUiType(uifile_2)
Class Home page:
class HomePage(base_1, form_1):
def __init__(self):
super(base_1,self).__init__()
self.setupUi(self)
#add button for click next page
self.btn_start = QPushButton(self)
self.btn_start.clicked.connect(self.change)
self._heightMask = self.height()
self.animation = QPropertyAnimation(self, b"heightPercentage")
self.animation.setDuration(1000)
self.animation.setStartValue(self.height())
self.animation.setEndValue(-1)
self.animation.finished.connect(self.close)
self.isStarted = False
def change(self):
self.plate = PlatePage()
self.plate.show()
self.close()
#pyqtProperty(int)
def heightMask(self):
return self._heightMask
#heightMask.setter
def heightPercentage(self, value):
self._heightMask = value
rect = QRect(0, 0, self.width(), self.heightMask)
self.setMask(QRegion(rect))
def closeEvent(self, event):
if not self.isStarted:
self.animation.start()
self.isStarted = True
event.ignore()
else:
self.closeEvent(self, event)
Class Plate Page
class PlatePage(base_2, form_2):
def __init__(self):
super(base_2, self).__init__()
self.setupUi(self)
self.show()
Please have a look and give me some solution.
Thank You
Try it:
from PyQt5.QtCore import QPropertyAnimation, QThread
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.resize(400, 400)
layout = QVBoxLayout(self)
layout.addWidget(QPushButton('Button', self))
self.animation = QPropertyAnimation(self, b'windowOpacity')
self.animation.setDuration(1000)
self.isStarted = False
self.doShow()
def doShow(self):
try:
self.animation.finished.disconnect(self.close)
except:
pass
self.animation.stop()
self.animation.setStartValue(0)
self.animation.setEndValue(1)
self.animation.start()
def closeEvent(self, event):
if not self.isStarted:
self.animation.stop()
self.animation.finished.connect(self.close)
self.animation.setStartValue(1)
self.animation.setEndValue(0)
self.animation.start()
self.isStarted = True
event.ignore()
else:
event.accept()
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())

Categories

Resources