I'm having a problem updating the icon of a button set with QToolButton. The idea is to use the button for a movie player. To play, one presses the button and the icon changes to pause. When pressed again, play is paused and the icon reverts to play. I have some working code, but the problem is that the icon is not updating consistently. If I keep the Qt window in focus, it takes one or two button presses to change the icon to the intended image, by which time the actual image is not the intended image (swapped play/pause).
Here is some minimal example code:
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QStyle, QToolButton
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent=parent)
self.play_button = QToolButton(clicked=self.update_button)
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.addWidget(self.play_button)
self.button_pressed = False
def update_button(self):
if self.button_pressed:
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.button_pressed = False
print("Button should be set to PLAY. Press is", self.button_pressed)
else:
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
self.button_pressed = True
print("Button should be set to PAUSE. Press is", self.button_pressed)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
In the above I start with a stop icon just to be sure to observe a change (any click should always change the icon). Keeping the window focused, I get the following output:
1st click: "Button should be set to PAUSE. Press is True" (no change in icon)
2nd click: "Button should be set to PLAY. Press is False" (icon changes to pause)
3rd click: "Button should be set to PAUSE. Press is True" (icon changes to play)
(and so on, continues swapping as intended)
I've also noticed that if after each click, I click outside the Qt window, or resize the Qt window, the button icon updates to the correct one. What am I doing wrong? How do I force the icon to update?
This behaviour happens mostly with QToolButton, but QPushButton also give issues (works when focused, but misbehaves/loses track of correct status if I resize the Qt window). Using PyQt 5.12.3 and qt 5.12.5 on macOS.
Seems like this issue is a bug in the Qt implementation for macOS. I tested and it happens with both PyQt5 and PySide2, so it must come from Qt. Forcing a redraw with a call to .repaint() after .setIcon() seems to make the problem go away:
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.play_button.repaint()
Related
Im just try to use PyQt5 for my robot GUI. im little bit confuse how to use momentary push button which should giving true value when it pressed down and false value when it unpressed. fyi im using python for this.
im already try to use "setCheckable" to detecting its state, but it make the button toggled (not momentary). is there any other methode that i can implement?
The clicked signal is always emitted after the mouse button is released in the button rectangle. That's the common convention for a "click event" of buttons in almost any reasonable UI toolkit.
If you want to know when the button is pressed or released, then you can connect to the pressed and released signals.
Be aware, though, that the common convention above also considers that if the user leaves the button while the mouse button is still pressed, the button becomes "unpressed" as well, and becomes pressed again if the mouse goes back inside its geometry while the same mouse button is still pressed. This means that if you connect to those signals and the user "leaves" the button with the mouse button still pressed and gets back to it, you might get inconsistent results depending on your needs.
If you are interested in the basic "just when mouse button was pressed" and "mouse button released at last" state, no matter if the user released the mouse button outside of the widget, then the only option is to override the mouse button handlers and use a custom signal:
from PyQt5 import QtCore, QtWidgets
class IgnoreClickPosButton(QtWidgets.QPushButton):
pressedState = QtCore.pyqtSignal(bool)
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.pressedState.emit(True)
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.pressedState.emit(False)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
btn = IgnoreClickPosButton()
btn.pressedState.connect(lambda p: print('pressed', p))
btn.show()
sys.exit(app.exec_())
I have two buttons and both of the has some hovering effect. The first button has a menu as well, and the problem is, when the first button is clicked and menu appears, the mouse hover doesn't work for the second button at the same time until the menu is closed.
I'm not sure, but I believe it is due to some sort of focusPolicy, and I tried to find the solution but I couldn't. I just want to make hovering effect on the buttons of the widget available even while showing the menu.
from PySide2 import QtWidgets, QtGui, QtCore
import sys
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.resize(300, 300)
layout = QtWidgets.QHBoxLayout(self)
btn = QtWidgets.QPushButton('Button 1')
btn.setStyleSheet('QPushButton::hover{background-color: gray;}')
layout.addWidget(btn)
menu = QtWidgets.QMenu(self)
action = QtWidgets.QAction('buttonAction', menu)
menu.addAction(action)
btn.setMenu(menu)
btn = QtWidgets.QPushButton('Button 2')
btn.setStyleSheet('QPushButton::hover{background-color: gray;}')
layout.addWidget(btn)
self.setLayout(layout)
app = QtWidgets.QApplication([])
wig = MyWidget()
wig.show()
sys.exit(app.exec_())
Please be noted, instead of stylesheet, I even tried using evenFilter changing the colors on Enter/Leave events and returning True/`False values.
TL; DR; The behavior you want is not possible.
Explanation:
Only one window can have the focus and only the widgets that belong to that window can get the focus. In this case, the QMenu lives in a different window that is on top of the original window and that window is the one with the focus and no longer the original window.
I am using PyQt5. I want to simulate a mouse click by a user on a specific QLineEdit widget programmatically, expecting the same behavior as for the real click.
The code below is my attempt, where this action is bound to a QPushButton. It works correctly when the QLineEdit widget target is focused. But if some other widget has focus when the button is pressed, both the focused widget and the target widget get the "focused" frame:
What should I do to avoid this problem and perform a simulated click correctly?
Note: it is not clear to me why calling .setFocus() in on_button_clicked below is necessary. I thought that a mouse click on a widget with a Qt.StrongFocus focus property is sufficient to switch focus, but the simulated click seems to be completely ignored by the target widget if the focus is not manually switched.
import functools
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt
app = QApplication([])
win = QMainWindow()
widget = QWidget()
win.setCentralWidget(widget)
layout = QVBoxLayout()
widget.setLayout(layout)
target = QLineEdit('Widget to be controlled')
layout.addWidget(target)
layout.addWidget(QLineEdit('(widget to test focus)'))
button = QPushButton('Click')
layout.addWidget(button)
def on_button_clicked(target):
# This minimal example always uses the same position in the widget
pos = QtCore.QPointF(25, 10)
# Transfer focus, otherwise the click does not seem to be handled
target.setFocus(Qt.OtherFocusReason)
press_event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, pos,
Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
target.mousePressEvent(press_event)
release_event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, pos,
Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
target.mouseReleaseEvent(release_event)
button.clicked.connect(functools.partial(on_button_clicked, target))
win.show()
app.exec_()
(If it helps or if there are easier ways to do this, the thing I am actually trying to do is to construct a widget which behaves almost like a QLineEdit, but completely ignores single mouse clicks (both press and release events), while the double click works like a single click on a QLineEdit. Transforming the double click event into a single click event on the underlying QLineEdit widget is the part I struggle with.)
I'm having a problem updating the icon of a button set with QToolButton. The idea is to use the button for a movie player. To play, one presses the button and the icon changes to pause. When pressed again, play is paused and the icon reverts to play. I have some working code, but the problem is that the icon is not updating consistently. If I keep the Qt window in focus, it takes one or two button presses to change the icon to the intended image, by which time the actual image is not the intended image (swapped play/pause).
Here is some minimal example code:
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QStyle, QToolButton
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent=parent)
self.play_button = QToolButton(clicked=self.update_button)
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.addWidget(self.play_button)
self.button_pressed = False
def update_button(self):
if self.button_pressed:
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.button_pressed = False
print("Button should be set to PLAY. Press is", self.button_pressed)
else:
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
self.button_pressed = True
print("Button should be set to PAUSE. Press is", self.button_pressed)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
In the above I start with a stop icon just to be sure to observe a change (any click should always change the icon). Keeping the window focused, I get the following output:
1st click: "Button should be set to PAUSE. Press is True" (no change in icon)
2nd click: "Button should be set to PLAY. Press is False" (icon changes to pause)
3rd click: "Button should be set to PAUSE. Press is True" (icon changes to play)
(and so on, continues swapping as intended)
I've also noticed that if after each click, I click outside the Qt window, or resize the Qt window, the button icon updates to the correct one. What am I doing wrong? How do I force the icon to update?
This behaviour happens mostly with QToolButton, but QPushButton also give issues (works when focused, but misbehaves/loses track of correct status if I resize the Qt window). Using PyQt 5.12.3 and qt 5.12.5 on macOS.
Seems like this issue is a bug in the Qt implementation for macOS. I tested and it happens with both PyQt5 and PySide2, so it must come from Qt. Forcing a redraw with a call to .repaint() after .setIcon() seems to make the problem go away:
self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.play_button.repaint()
Consider the following code snippet using Python 3 and PyQt 5.5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
d = QDialog()
l = QLineEdit(d)
w.show()
# Comment the following line to gain focus.
d.setWindowFlags(Qt.Popup)
d.show()
app.exec_()
After d.show() is invoked, the dialog is shown but the QLineEdit inside it doesn't have focus. No amount of raise_(), activateWindow() or setFocus() seems to be working. How can I make the dialog automatically gain focus when it's shown? I would like to keep the dialog as Qt.Popup, because I need it to close when I click outside of it.
There is
QWidget::raise();
QWidget::activateWindow();
From the docs:
Sets the top-level widget containing this widget to be the active window.
An active window is a visible top-level window that has the keyboard input focus.
This function performs the same operation as clicking the mouse on the title bar of a top-level window. On X11, the result depends on the Window Manager. If you want to ensure that the window is stacked on top as well you should also call raise(). Note that the window must be visible, otherwise activateWindow() has no effect.
On Windows, if you are calling this when the application is not currently the active one then it will not make it the active window. It will change the color of the taskbar entry to indicate that the window has changed in some way. This is because Microsoft does not allow an application to interrupt what the user is currently doing in another application.
It seems that you need to set focus on the line-edit after the dialog is shown:
l = QLineEdit(d)
w.show()
d.setWindowFlags(Qt.Popup)
d.show()
l.setFocus()
app.exec_()
If that doesn't work, try it with a timer:
QTimer.singleShot(1, l.setFocus)