I want to take an action when a widget was resized.
Is there a way to catch that without installing an event filter on that widget (and, obviously, without subclassing it)? AFAIK, QWidget does not have a resized signal.
You can derive from widget class and reimplement resizeEvent event
If you have any other QObject that can have strict relation to that QWidget you may use QObject::installEventFilter(QObject * filter) and overload bool eventFilter(QObject *, QEvent *). See more at Qt docs
In case you are using Python with PyQt4, you can set widget.resizeEvent to your function without sublclassing it:
#!/usr/bin/env python
import sys
from PyQt4 import QtCore, QtGui
def onResize(event):
print event
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
widget = QtGui.QPushButton('Test')
widget.resizeEvent = onResize
widget.resize(640, 480)
widget.show()
sys.exit(app.exec_())
Sorry, it looks like a hack, but I use this:
some_widget.resizeEvent = (lambda old_method: (lambda event: (self._on_resized(event), old_method(event))[-1]))(some_widget.resizeEvent)
This is a couple of years too late, but I was working on a transparent overlay widget that would completely cover the parent. You can not do what you want without subclassing, but you can restrict the subclassing to an instance as #reclosedev suggests, meaning that you don't have to actually create a subclass.
I wrote the following snippet (which works in PyQt4) for following the size of any widget that the widget is added to:
class TransparentOverlay(QtGui.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self._updateParent(self.parentWidget())
def setParent(self, parent, *args):
prevParent = self.parentWidget()
super().setParent(parent, *args)
self._updateParent(parent, prevParent)
def unsetParent(self, parent=None):
if parent is None:
parent = self.parentWidget()
if parent is not None and hasattr(parent.resizeEvent, '_original'):
parent.resizeEvent = parent.resizeEvent._original
def _updateParent(self, parent, prevParent=None):
if parent is not prevParent:
self.unsetParent(prevParent)
if parent is not None:
original = parent.resizeEvent
def resizeEventWrapper(event):
original(event)
self.resize(event.size())
resizeEventWrapper._original = original
parent.resizeEvent = resizeEventWrapper
self.resize(parent.size())
This code uses a couple of neat tricks that are possible with Python:
The original method is stashed in the _original attribute of the new one. This is possible because functions are objects.
The new method truly subclasses any QWidget instance, meaning that you do not have to create an actual subclass. Each parent instance will effectively become an instance of a subclass by virtue of the tacked on method.
If you need a one-time thing, all of the code for removing the subclassed resizeEvent method and replacing it with the original can be trashed. In that case, the solution is basically a fancier version of #reclosedev's solution, but with #Chris's comments about preserving the original addressed.
The only caveat with this code is that it does not support GL widgets correctly, so for example the overlay can not always be added to the viewport of a QGraphicsView. It can, however, be added to the QGraphicsView itself.
You can override the resizeEvent by
def resizeEvent(self, newSize):
#do code here
Related
When I set focus on a widget and then check whether it has focus, the result is False instead of True. When the application loads, however, the desired widget has focus.
from PySide2 import QtCore, QtWidgets, QtGui
app = QtWidgets.QApplication([])
widget = QtWidgets.QWidget()
button = QtWidgets.QPushButton()
line_edit = QtWidgets.QLineEdit()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(button)
layout.addWidget(line_edit)
widget.setLayout(layout)
widget.show()
print(f"{line_edit.hasFocus()}") # False
line_edit.setFocus(QtCore.Qt.OtherFocusReason) # without this, nothing has focus
print(f"{line_edit.hasFocus()}") # Expected True, actually False!
app.exec_()
How can I set focus in such a way that I could test it?
EDIT: I'm running these versions:
>>> import PySide2
>>> from PySide2 import QtCore, QtWidgets, QtGui
>>> print("Qt: ", PySide2.QtCore.__version__, " PySide2: ", PySide2.__version__)
Qt: 5.15.2 PySide2: 5.15.2
EDIT2: If I create a shortcut which prints the focus status, the result is True. I suspect that is because the app is executing. It still stands, how could I test this?
QtWidgets.QShortcut(QtGui.QKeySequence("F6"), widget, lambda: print(f"{line_edit.hasFocus()}"))
In order to test whether a widget has focus, the QCoreApplication event loop must be running and the widget must be visible. Focus is the result of an event and it only makes sense to talk about focus if the widget is showing (Qt seems to require the widget be visible for a focus event to occur). This presents a problem for running tests without user interaction: how do we automatically exit the main event loop? The answer is in the question; by using events.
QCoreApplication.exec_() starts the main event loop. Events are put on a queue and processed in turn (according to a priority). The loop goes on forever until it receives an event telling it to stop. Use postEvent to add an event to the queue. There are different handlers for different events. The QWidget.focusInEvent method is called when the widget takes focus. Other events pass through the general QObject.eventFilter method.
We can install a custom event filter on a QObject using its installEventFilter method. This method takes a QObject with a custom eventFilter:
class CustomEventFilterObject(QObject):
def eventFilter(self, obj, event):
if event.type() == QEvent.User:
print("Handle QEvent types")
return True
else:
# standard event processing
return QObject.eventFilter(self, obj, event)
# ...
custom_event_filter = CustomEventFilterObject()
widget.installEventFilter(custom_event_filter)
Armed with this knowledge, we can contrive a solution (and is it ever contrived!).
Here is the original question, modified so that the show method is no longer called from this module:
# focus_problem.py
from PySide2 import QtCore, QtWidgets, QtGui
widget = QtWidgets.QWidget()
button = QtWidgets.QPushButton()
line_edit = QtWidgets.QLineEdit()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(button)
layout.addWidget(line_edit)
widget.setLayout(layout)
# line_edit.setFocus(QtCore.Qt.OtherFocusReason) # without this, nothing has focus
We know that the line_edit focus is only testable after app.exec_() has started the event loop. Create a custom focusInEvent function and attach it to the line_edit. This will get called if the line_edit takes focus. If that happens, exit the application with a return value indicating success. In case focus never lands on the line_edit, create a custom eventFilter on the widget. Post an event that closes the application with a return value indicating failure. Make sure this event is posted after the focus is set. Since focus is set on the line_edit when the module is imported, this won't be an issue for us.
Using unittest, the test looks like:
# test_focus_problem.py
#
# Run with:
#
# python3 -m unittest discover focus_problem/ --failfast --quiet
import unittest
from PySide2 import QtCore, QtWidgets, QtGui, QtTest
# need a QApplication in order to define the widgets in focus_problem
# module
if not QtWidgets.QApplication.instance():
QtWidgets.QApplication([])
import focus_problem
class TestFocusEvent(unittest.TestCase):
# called before the body of test_application_starts_with_line_edit_in_focus is executed
def setUp(self):
def focusInEvent(event):
QtWidgets.QApplication.instance().exit(0) # Success
# override the focus event handler
focus_problem.line_edit.focusInEvent = focusInEvent
class KillApplicationIfLineEditNeverTakesFocus(QtCore.QObject):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.User:
QtWidgets.QApplication.instance().exit(1) # Fail
return True
else:
return QtCore.QObject.eventFilter(self, obj, event)
custom_filter = KillApplicationIfLineEditNeverTakesFocus()
focus_problem.widget.installEventFilter(custom_filter)
focus_problem.widget.show()
QtWidgets.QApplication.instance().postEvent(focus_problem.widget, QtCore.QEvent(QtCore.QEvent.User))
self.rv = QtWidgets.QApplication.instance().exec_()
def test_application_starts_with_line_edit_in_focus(self):
self.assertEqual(self.rv, 0)
The application is started using setUp on a dedicated test class. This allows test_application_starts_with_line_edit_in_focus to collect the return value and report it along with other tests. It checks to see if the application was killed by the focusInEvent (a success) or the eventFilter (a failure).
I'd be surprised if there wasn't an easier way to do all this. :)
On Ubuntu20.04, I can't have the widget receive the mouse release event when context menu appears while Windows can receive. My pyqt version is 5.15.2.
I've considered sending a mouse release event manually, but I don't know which systems will receive the mouse release event when context menu appears, and doing so may cause repeated release events. Is there any better solution?
# coding:utf-8
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMenu, QLabel
class Menu(QMenu):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.addAction('action 1')
self.addAction('action 2')
def showEvent(self, e):
print('show context menu')
super().showEvent(e)
class Widget(QLabel):
def mousePressEvent(self, e):
print('mouse press, button:', e.button())
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
print('mouse release, button:', e.button())
super().mouseReleaseEvent(e)
class Demo(QWidget):
def __init__(self):
super().__init__()
self.widget = Widget('Click Me', self)
self.widget.move(175, 180)
self.resize(400, 400)
def contextMenuEvent(self, e):
menu = Menu(self)
menu.exec(e.globalPos())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Demo()
w.show()
app.exec_()
The general rule is that the context menu event is fired when the right mouse buttons is released on Windows and pressed on Linux.
To avoid inconsistency and have a generic behavior that just ignores the OS and always does the same, you can set an internal flag and check it whenever is required: since the context menu event might take precedence, call a function from both mouse press and context menu.
Remember that:
if you don't want to handle the context menu event and want it to propagate to the parent (like in your case), the event must be ignored; if you override contextMenuEvent this must be done explicitly either with event.ignore() or by calling the base implementation, assuming that it does not accept the event;
QLabel might handle the event if the text interaction flag supports mouse/keyboard navigation (to allow the clipboard menu);
context menu event can also be triggered by the menu-key on the keyboard, so you should ensure that its reason() is Mouse; this is not actually required if the function that does the "unpress" part always check for the above flag (and it should), but it's still good practice to check for the reason for completeness and consistency;
class Widget(QLabel):
_isPressed = False
def mousePressEvent(self, e):
print('mouse press, button:', e.button())
super().mousePressEvent(e)
self._isPressed = True
def mouseReleaseEvent(self, e):
super().mouseReleaseEvent(e)
self.unpress()
def contextMenuEvent(self, e):
super().contextMenuEvent(e)
# or, alternatively:
e.ignore()
if e.reason() == e.Mouse:
self.unpress()
def unpress(self):
if not self._isPressed:
return
self._isPressed = True
# do whatever you need
I want to implement the following functionality into my Qt application:
User opens one or more 'input' widgets (instances of an InputWidget class), each containing a QLineEdit widget
User opens a 'helper' dialog
User selects a value in the 'helper' dialog
User presses the 'Insert' QPushButton in the 'helper' dialog
The selected value from the 'helper' dialog' is inserted into the QLineEdit of that 'input' dialog that had the last focus before the 'Insert' button was pressed
So, basically, what I want is that if the user clicks on 'Insert' in the following screenshot, the string 'Apple' should appear in the focused input dialog. The code example below does kind of work, only that the string is (usually, see below) inserted into the second one.
Here's is the code example that creates this setup:
from PyQt5.QtWidgets import (QApplication, QWidget, QHBoxLayout,
QLineEdit, QLabel, QPushButton, QComboBox)
import sys
# this is the missing bit
def determineWhichWidgetHadLastFocus():
for widget in QApplication.instance().topLevelWidgets():
if isinstance(widget, InputWidget):
# do something wonderful to determine whether this widget
# is the one that had last focus
wonderful = True
if wonderful:
return widget
return None
class BaseWidget(QWidget):
""" Base widget type """
def __init__(self, name):
super(BaseWidget, self).__init__()
self.setWindowTitle(name)
self.setupUi()
self.show()
def setupUi(self):
pass
class InputWidget(BaseWidget):
""" InputWidget contains a QLabel and a QLineEdit widget """
def setupUi(self):
self.label = QLabel("Input string:")
self.edit = QLineEdit()
layout = QHBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.edit)
class HelperWidget(BaseWidget):
""" HelperWidget contains a QLineEdit and a QPushButton widget. Pressing
the button inserts the content of the edit widget into the edit widget of
the last activated InputWidget """
def setupUi(self):
self.combo = QComboBox()
self.combo.addItems(["Apple", "Pear", "Banana"])
self.button = QPushButton("Insert")
self.button.clicked.connect(self.insertString)
layout = QHBoxLayout(self)
layout.addWidget(self.combo)
layout.addWidget(self.button)
def insertString(self):
widget = determineWhichWidgetHadLastFocus()
if widget:
widget.edit.insert(self.combo.currentText())
def main():
app = QApplication(sys.argv)
diag1 = InputWidget("Input dialog")
diag2 = InputWidget("Another input")
helper = HelperWidget("Helper")
app.exec_()
if __name__ == "__main__":
main()
The missing part is the determineWhichWidgetHadLastFocus() function.
This function is supposed to do something wonderful that allows it to determine which 'input' was the last to hold focus. Currently, it traverses the list of top level widgets from QApplication, but the order of top level widgets doesn't reflect the activation order (it usually, but not always appears to be the order of creation).
One idea that came to my mind was to install an event filter that keeps track of the FocusIn events. That would be easy for the InputWidget class in my example, but might not work so well for my real application that has many QLineEdits, QTextEdits and descended classes all over the place. I'd rather not go that way.
Any other ideas?
It turns out that the event filter idea is the way to go after all. What I did was first create an event filter class that emits a signal if the sending object is a QLineEdit object:
from PyQt5.QtCore import QObject, QEvent, pyqtSignal
class inputFocusFilter(QObject):
focusIn = pyqtSignal(object)
def eventFilter(self, widget, event):
if event.type() == QEvent.FocusIn and isinstance(widget, QLineEdit):
# emit a `focusIn` signal, with the widget as its argument:
self.focusIn.emit(widget)
return super(inputFocusFilter, self).eventFilter(widget, event)
This filter is installed for a custom QApplication class so that any event that is created passes the filter. The signal focusIn is connected to a setter function that remembers the last input widget that received focus:
class MyApplication(QApplication):
def __init__(self, *arg, **kwarg):
super(MyApplication, self).__init__(*arg, **kwarg)
self._input_focus_widget = None
self.event_filter = inputFocusFilter()
self.event_filter.focusIn.connect(self.setInputFocusWidget)
self.installEventFilter(self.event_filter)
def setInputFocusWidget(self, widget):
self._input_focus_widget = widget
def inputFocusWidget(self):
return self._input_focus_widget
MyApplication is used instead of QApplication in the first line of main(). Now, the call to determineWhichWidgetHadLastFocus() in HelperWidget.insertString() can be replaced by QApplication.instance().inputFocusWidget(), and everything works as intended.
How to have all attributes and Qt views of an object updated when reloading the python module that defines this object, without breaking or even touching the links between this object and other objects?
Here is an exemple:
The QMainWindow of a Qt application includes both local widgets and a reloadable widget:
from PyQt4 import QtCore, QtGui
import reloadable
name='Test reloadable'
class MyWindow(QtGui.QMainWindow):
def __init__(self):
# Initialize the main window
super(MyWindow, self).__init__()
self.setWindowTitle(name)
# Create an instance of the reloadable widget
self.reloadableWidget= reloadable.ReloadabelWidget()
# Create a local widget that can ask for and display an attribute of the reloadable widget
button1 = QtGui.QPushButton('Identify')
button1.clicked.connect(self.identifyReloadable)
self.label1=QtGui.QLabel('')
layout1 = QtGui.QHBoxLayout()
layout1.addWidget(button1)
layout1.addWidget(self.label1)
localWidget=QtGui.QWidget()
localWidget.setLayout(layout1)
# Put the local widget and the reloadable widget in the window
centralWidget=QtGui.QWidget()
layout = QtGui.QVBoxLayout()
layout.addWidget(localWidget)
layout.addWidget(self.reloadableWidget)
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
def identifyReloadable(self):
identity=self.reloadableWidget.identify()
self.label1.setText(identity)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName(name)
main = MyWindow()
main.show()
sys.exit(app.exec_())
This reloadable widget (QFrame) has a method 'identify()' called by main and a method 'reload()' called by itself. This reload method essentially reloads the class module, replace the class attribute by the possibly updated module.class, and tries to update the Qt views of the object.
# This is module reloadble.py
from PyQt4 import QtCore, QtGui
import inspect
identity='1'
color='blue'
class ReloadabelWidget(QtGui.QFrame,object):
def __init__(self):
QtGui.QFrame.__init__(self)
self.setFrameStyle(1)
self.reinit()
def reinit(self):
button = QtGui.QPushButton('Reload',self)
button.clicked.connect(self.reload)
label=QtGui.QLabel(identity)
label.setStyleSheet("QLabel { background-color : white; color : %s; }" % color);
layout = QtGui.QHBoxLayout()
layout.addWidget(button)
layout.addWidget(label)
self.setLayout(layout)
self.setUpdatesEnabled(True)
def identify(self):
return identity # will change each time the file is edited with a new identity in line 3
def reload(self):
module=inspect.getmodule(self)
reload(module)
self.reinit() # object is reinitialized
self.__class__= module.ReloadabelWidget # callable attributes are now up to date
self.update() # naive attempt to update the Qt view => does not work
# add code here to have the GUI part of self, i.e. the QFrame updated ...
# ... whereever it is displayed.
The main application is running and the programmer redefines the method identify() and the color of a label by editing and saving the code of the reloadable module.
identity='2'
color='red'
The reload button is then clicked to run the reload() method of the reloadable widget. Its methods are properly updated and clicking the identify button displays the updated identity 2. But the widget's view is not updated because I don't know how to do it. What are the lines to be added at the end of the reload method to obtain the redisplay of the new interface, created by self.reinit()
?
PS1: For those who like to know what is the goal behind a question, I want to accelerate the development of a complicated multi-module application by having both the attributes and the gui of a module updated at code edition, without restarting the application.
PS2: This question replaces a bad one that I had asked without being sufficiently explicit. Since there was already several answers, I make this new post. I now provide a complete code entirely tested and make my point clearly.
I have a list of dictionaries:
globalParams = [{'attr':'enabled','ctrl':'checkBoxEnabled','type':'checkBox'},
{'attr':'colorMode','ctrl':'comboBoxColorMode','type':'comboBox'}]
'ctrl' - name of the control in the Qt window.
typically, the code is as follows:
self.checkBoxEnabled.checkState()
but checkBoxEnabled is an object. and i have only a string name 'checkBoxEnabled' and cannot use it...
how to find an object by name in pyqt? something like?
self.GetObjectByName('checkBoxEnabled').checkState()
You can use QObject::findChild method. In pyqt it should be written like this:
checkbox = self.findChild(QtGui.QCheckBox, "checkBoxEnabled")
self should be a parent widget of the checkbox.
To find all the QCheckBox items in your UI you can run a loop through all the children of the widget like as below:
from PyQt5.QtWidgets import QCheckBox
from PyQt5.QtCore import QObject
class Mainwindow(QMainWindow):
def __init__(self):
# super().__init__()
self.checkbox = QCheckBox(self)
self.checkbox_1 = QCheckBox(self)
for checkstate in self.findChildren(QCheckBox):
print(f'get check state:{checkstate.checkState()}')