How to transfer mouse focus back while showing a QMenu? - python

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.

Related

Sending a fake mouse click to an unfocused QLineEdit widget with PyQt

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.)

pyqt5's setPlainText / appendPlainText doesn't show text right away [duplicate]

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()

PyQt QToolButton not updating icon when in focus

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()

How to add scrollbars in PyQt4 WITHOUT with absolute positioning

I'm creating an interface in PyQt4, and I am in need of scrollbars. My interface uses absolute positioning. I have looked a other treads for adding scrollbars, but the only answers given are to those interfaces without a layout (such as VBoxLayout, Grid Layout, etc).
Please take a look at my code. How could I add a scrollbars (horizontal and vertical) to this interface?
The full code wouldn't format properly on here, so I'll link to this pastebin
http://pastebin.com/hEH4R534
Here is the base of the interface (a 1500px by 1000px window)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(100,100,1500,1000)
def main():
import sys
app = QtGui.QApplication(sys.argv)
window = Example()
window.show()
sys.exit(app.exec_())
main()
The question is... How would I modify the code above so I have horizontal and vertical scrollbars?
Only widgets that inherit from QAbstractScrollArea can have scroll bars. You could place one of those widgets inside your widget, and then place other widgets inside that widget. Or, just have your widget inherit from QScrollArea instead of QWidget.
By default, scroll bars will only appear when necessary to display hidden child widgets. You can force scroll bars to appear by setting the scroll bar policy for the widget.
from PyQt4.QtCore import Qt
...
def initUI(self):
self.setGeometry(100, 100, 1500, 1000)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

PyQt: getting widgets to resize automatically in a QDialog

I'm having difficulty getting widgets in a QDialog resized automatically when the dialog itself is resized.
In the following program, the textarea resizes automatically if you resize the main window. However, the textarea within the dialog stays the same size when the dialog is resized.
Is there any way of making the textarea in the dialog resize automatically? I've tried using setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) on the dialog itself and the two widgets within, but that seems to have no effect.
I'm using Qt version 3.3.7 and PyQt version 3.5.5-29 on openSuSE 10.2, if that's relevant.
import sys
from qt import *
# The numbers 1 to 1000 as a string.
NUMBERS = ("%d " * 1000) % (tuple(range(1,1001)))
# Add a textarea containing the numbers 1 to 1000 to the given
# QWidget.
def addTextArea(parent, size):
textbox = QTextEdit(parent)
textbox.setReadOnly(True)
textbox.setMinimumSize(QSize(size, size*0.75))
textbox.setText(NUMBERS)
class TestDialog(QDialog):
def __init__(self,parent=None):
QDialog.__init__(self,parent)
self.setCaption("Dialog")
everything = QVBox(self)
addTextArea(everything, 400)
everything.resize(everything.sizeHint())
class TestMainWindow(QMainWindow):
def __init__(self,parent=None):
QMainWindow.__init__(self,parent)
self.setCaption("Main Window")
everything = QVBox(self)
addTextArea(everything, 800)
button = QPushButton("Open dialog", everything)
self.connect(button, SIGNAL('clicked()'), self.openDialog)
self.setCentralWidget(everything)
self.resize(self.sizeHint())
self.dialog = TestDialog(self)
def openDialog(self):
self.dialog.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainwin = TestMainWindow(None)
app.setMainWidget(mainwin)
mainwin.show()
app.exec_loop()
QMainWindow has special behavior for the central widget that a QDialog does not. To achieve the desired behavior you need to create a layout, add the text area to the layout and assign the layout to the dialog.
Just to add a little note about this - I was trying to have a child window spawned from an application, which is a QDialog, containing a single QTextEdit as a child/content - and I wanted the QTextEdit to resize automatically whenever the QDialog window size changes. This seems to have done the trick for me with PyQt4:
def showTextWindow(self):
#QVBox, QHBox # don't exist in Qt4
dialog = QDialog(self)
#dialog.setGeometry(QRect(100, 100, 400, 200))
dialog.setWindowTitle("Title")
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
textbox = QTextEdit(dialog)
textbox.setReadOnly(True)
textbox.setMinimumSize(QSize(400, 400*0.75))
textbox.setText("AHAAA!")
# this seems enough to have the QTextEdit
# autoresize to window size changes of dialog!
layout = QHBoxLayout(dialog)
layout.addWidget(textbox)
dialog.setLayout(layout)
dialog.exec_()
I had looked at using a QLayout before but had no luck. I was trying to do something like
dialog.setLayout(some_layout)
but I couldn't get that approach to work so I gave up.
My mistake was that I was trying to pass the layout to the dialog when I should have been passing the dialog to the layout.
Adding the lines
layout = QVBoxLayout(self)
layout.add(everything)
to the end of TestDialog.__init__ fixes the problem.
Thanks to Monjardin for prompting me to reconsider layouts.
Check out Python QT Automatic Widget Resizer It's suppose to work well.

Categories