I understand that QEvent::ShortcutOverride occurs when there is a shortcut registered in the parent and the child wants to "go against the rule". The example given on the Qt Wiki is of a media player which pauses with Space but a QLineEdit might want to use the Space, which makes a lot of sense.
Furthermore, if the event is accepted then a QEvent::KeyPress is generated to the child widget so you can treat your particular case.
Now, my question is, why does it seem that the default action is to actually accept the QEvent::ShortcutOverride when a standard shortcut is used? This seems to me like the opposite of what the name suggest, i.e., it's overriden by default and you have to treat the event to let the shortcut pass.
In the code below, if you don't install the event filter you don't see the message.
from PySide.QtGui import QApplication
from PySide import QtGui, QtCore
app = QApplication([])
class Test(QtGui.QWidget):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
self.w_edit = QtGui.QLineEdit(parent=self)
self.layout().addWidget(self.w_edit)
# If we install the event filter and ignore() the ShortcutOverride
# then the shortcut works
self.w_edit.installEventFilter(self)
# Ctrl+Left is already in use (jump to previous word)
shortcut = QtGui.QShortcut(QtGui.QKeySequence('Ctrl+Left'), self)
shortcut.setContext(QtCore.Qt.ApplicationShortcut)
shortcut.activated.connect(self.test_slot)
def test_slot(self):
print('ctrl+left pressed!')
def eventFilter(self, obj, event):
if obj is self.w_edit and event.type() == QtCore.QEvent.ShortcutOverride:
# Send the event up the hierarchy
event.ignore()
# Stop obj from treating the event itself
return True
# Events which don't concern us get forwarded
return super(Test, self).eventFilter(obj, event)
widget = Test()
widget.show()
if __name__ == '__main__':
app.exec_()
My actual scenario is a tab widget for which I want to use Ctrl+Left/Right to cycle through the tabs, which works unless something like a QLineEdit has focus. I feel that there should be a better way other than calling event->ignore(); return true on all QLineEdits and any other widgets which could use the key combo, am I missing something here?
Thanks!
You can set one event filter on the application instance and then filter accordingly:
QtGui.qApp.installEventFilter(self)
# self.w_edit.installEventFilter(self)
...
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.ShortcutOverride:
# filter by source object, source.parent(), or whatever...
if isinstance(source, QtGui.QLineEdit):
event.ignore()
return True
return super(Test, self).eventFilter(source, event)
Related
I am working on a project with PyQt5 which has QFrames. I am using mouse press event to trigger a function on clicking frame as below :
frame.mousePressEvent = lambda x: print_name(x, name)
Above line is not executed at the start, It is executed after user has done some work in UI.
I am getting the behaviour I want but here is the problem:
If the user clicks the frame after the above line of code is executed, it works fine but if the user clicks on the frame before the above line of the code is executed and later again clicks the frame (after code is executed), I am not getting the same behaviour. Basically nothing happens.
I want to know where is the problem and how do I solve it?
The problem is caused because PyQt5 caches the methods so if the method is assigned then it cannot be changed. Instead of following the bad practice of assigning methods to mousePressEvent there are other better alternatives such as:
Implement inheritance
class Frame(QFrame):
def mousePressEvent(self, event):
super().mousePressEvent(event)
print(event)
Use an event filter
class MouseObserver(QObject):
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, obj, event):
if obj is self.widget and event.type() == QEvent.MouseButtonPress:
print(event)
return super().eventFilter(obj, event)
Then
observer = MouseObserver(frame)
The second seems the most appropriate for your case.
On the right of the title bar of a PyQt QDialog (see below, next to the "x") there is a "?" that is supposed to help the user query help for any other widget on the Dialog window.
What should I do (programmatically) to get it to work. Once the "?" isClicked, one should be able to capture the next widget clicked and provide a ToolTip or something like that. In PyQt, I do not know how to capture the isClicked event on the "?".
I have seen a couple of posts where the question was how to make the "?" disappear, but the discussion there uses Qt, not PyQt, so I do not understand it, and they are not talking about what I need. I need to make it work as intended. See How can I hide/delete the "?" help button on the "title bar" of a Qt Dialog? and PyQt4 QInputDialog and QMessageBox window flags
You can set the whatsThis property to any widget you want:
self.someWidget.setWhatsThis('hello!')
From that point on, whenever you click on the "?" button and then click on that widget, a tooltip with that text will be shown.
Since the "what's this" mode is individually set to widgets, there's no easy way to capture it globally (as far as I know of) because if the widget has no whatsthis property set that feature won't be available for it.
Also, whenever you enter the "what's this" mode, the cursor will probably change according to the contents of the whatsthis property: if it's not set, the cursor will probably show a "disabled" icon.
I've created a basic workaround for this issue, which automatically enables any child widget's whatsthis (if none is already set) whenever the mode is activated: as soon as the EnterWhatsThisMode is fired, it automatically installs a custom object that acts as an event filter, and emits a signal if the whatsthis event is called; as soon as the mode exits, the filter is removed.
I used a separate object for the event filter because there's no way to know what event filter have been already installed to a widget, and if you already installed the parent's one, removing it automatically would be an issue.
class WhatsThisWatcher(QtCore.QObject):
whatsThisRequest = QtCore.pyqtSignal(QtWidgets.QWidget)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.WhatsThis:
self.whatsThisRequest.emit(source)
return super(WhatsThisWatcher, self).eventFilter(source, event)
class W(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QVBoxLayout(self)
hasWhatsThisButton = QtWidgets.QPushButton('Has whatsThis')
layout.addWidget(hasWhatsThisButton)
hasWhatsThisButton.setWhatsThis('I am a button!')
noWhatsThisButton = QtWidgets.QPushButton('No whatsThis')
layout.addWidget(noWhatsThisButton)
self.whatsThisWatchedWidgets = []
self.whatsThisWatcher = WhatsThisWatcher()
self.whatsThisWatcher.whatsThisRequest.connect(self.showCustomWhatsThis)
whatsThisButton = QtWidgets.QPushButton('Set "What\'s this" mode')
layout.addWidget(whatsThisButton)
whatsThisButton.clicked.connect(QtWidgets.QWhatsThis.enterWhatsThisMode)
def event(self, event):
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
for widget in self.findChildren(QtWidgets.QWidget):
if not widget.whatsThis():
# install the custom filter
widget.installEventFilter(self.whatsThisWatcher)
# set an arbitrary string to ensure that the "whatsThis" is
# enabled and the cursor is correctly set
widget.setWhatsThis('whatever')
self.whatsThisWatchedWidgets.append(widget)
elif event.type() == QtCore.QEvent.LeaveWhatsThisMode:
while self.whatsThisWatchedWidgets:
widget = self.whatsThisWatchedWidgets.pop()
# reset the whatsThis string to none and uninstall the filter
widget.setWhatsThis('')
widget.removeEventFilter(self.whatsThisWatcher)
return super(W, self).event(event)
def showCustomWhatsThis(self, widget):
widgetPos = widget.mapTo(self, QtCore.QPoint())
QtWidgets.QWhatsThis.showText(
QtGui.QCursor.pos(),
'There is no "what\'s this" for {} widget at coords {}, {}'.format(
widget.__class__.__name__, widgetPos.x(), widgetPos.y()),
widget)
A couple of notes about this:
I used a button to activate the whatsthis mode, as on my window manager on Linux there's no window title button for that;
Some widgets may contain subwidgets, and you'll get those instead of the "main" one (the most common case are QAbstractScrollArea descendands, like QTextEdit or QGraphicsView, which might return the viewport, the inner "widget" or the scrollbars);
By default the task of that button is to enable whatsThis: press "?", then press the widget and you will see the message associated with whatsThis property.
If you want to add other actions(open url, add QToolTip, etc) you can monitor the QEvent::EnterWhatsThisMode and QEvent::LeaveWhatsThisMode events overriding the event() method or using an eventFilter().
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def event(self, event):
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
print("enter")
QtGui.QDesktopServices.openUrl(QtCore.QUrl("foo_url"))
elif event.type() == QtCore.QEvent.LeaveWhatsThisMode:
print("leave")
return super().event(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.setWhatsThis("Whats this")
w.setWindowFlags(
QtCore.Qt.WindowContextHelpButtonHint | QtCore.Qt.WindowCloseButtonHint
)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I have an eventFilter on my custom label and I would like to sniff double clicks with it. Is this possible?
self.installEventFilter(self)
# Handles mouse events
def eventFilter(self, object, event):
try:
if event.buttons() == QtCore.Qt.LeftButton:
#LeftButton event
else:
# nothing is being pressed
except:
pass
Yes it is possible but for some strange reason it is not that simple. Surely you never know if a single click might be followed by another single click effectively resulting in a double click. That's why there must be some inbuilt waiting time. Qt does it and delivers events for double clicks (QEvent.MouseButtonDblClick). On the other hand Qt still delivers events for single clicks (QEvent.MouseButtonPress) even in the case of a double click, but only one. This might not be the best design.
We must differentiate them correctly. I do it with an additional timer that needs to be a bit longer than the inbuilt Qt timer for detecting double clicks. The code then goes:
from PySide import QtCore, QtGui
class MyLabel(QtGui.QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.installEventFilter(self)
self.single_click_timer = QtCore.QTimer()
self.single_click_timer.setInterval(200)
self.single_click_timer.timeout.connect(self.single_click)
def single_click(self):
self.single_click_timer.stop()
print('timeout, must be single click')
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
self.single_click_timer.start()
return True
elif event.type() == QtCore.QEvent.MouseButtonDblClick:
self.single_click_timer.stop()
print('double click')
return True
return False
app = QtGui.QApplication([])
window = MyLabel('Click me')
window.resize(200, 200)
window.show()
app.exec_()
See also Distinguish between single and double click events in Qt.
PyQt4 how to do if the window is minimized, call a method expand that window
please tell me how to do it
I will be very grateful
You can check the current state of a QWidget by calling its windowState() method. To change the state you pass a new state to setWindowState().
Here's an example app that checks every 5 seconds to see if it's minimised. If it is then the window is restored.
This is just as an example - checking every 5 seconds for a minimised window and restoring it would be an evil thing to do in an app ;).
import sys
import time
from PyQt4.QtGui import QApplication, QWidget
from PyQt4.QtCore import QTimer, Qt
class MyWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.timer = QTimer()
self.timer.setInterval(5000)
self.timer.timeout.connect(self.check_state)
self.timer.start()
def check_state(self):
if self.windowState() == Qt.WindowMinimized:
# Window is minimised. Restore it.
self.setWindowState(Qt.WindowNoState)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
The most accurate way to do this would be to watch for the QWindowStateChangeEvent of the widget, and respond immediately when it happens. You can do this more than one way.
Here is how you can re-implement the event method of the target widget:
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
def event(self, e):
if e.type() == e.WindowStateChange:
if self.windowState() & QtCore.Qt.WindowMinimized:
print "Minimized"
# self.showMaximized()
# call the super class event() no matter what
return super(Window, self).event(e)
Now if you have some other widget that you want to watch for Minimize, and you don't want to have to define a new event method on that object, you can create an object that simply watches events for multiple other objects. It is called an event filter:
class Watcher(QtCore.QObject):
def eventFilter(self, obj, e):
if obj.isWidgetType() and e.type() == e.WindowStateChange:
if obj.windowState() & QtCore.Qt.WindowMinimized:
print "Minimized"
# obj.showMaximized()
return False
app = QtGui.QApplication([])
aWindow = QtGui.QWidget()
aWatcher = Watcher(aWindow)
aWindow.installEventFilter(aWatcher)
aWindow.show()
app.exec_()
Note that when checking the windowState, you should use & to compare instead of ==, because the state can be a combination of more than one value and you need to check it with a mask to see if the value is present amongst the others. i.e. if you maximize the window first and then minimize it, it will have multiple window states.
On a side note, you also have the option of customizing the actual window properties in the first place. So if your goal is to prevent minimizing, you could disable the button for it:
aWindow = QtGui.QWidget()
flags = aWindow.windowFlags()
aWindow.setWindowFlags(flags ^ QtCore.Qt.WindowMinimizeButtonHint)
This will subtract the minimize button flag from all the other flags.
Hi guys me better come this way:
if self.windowState() == QtCore.Qt.WindowMinimized:
# Window is minimised. Restore it.
self.setWindowState(QtCore.Qt.WindowActive)
certainly not always this function is `working
probably the problem in the python
**thanks to all**
I am trying to display the value of a Boolean variable using a QCheckBox widget, and render the user unable to change the displayed value. I don't want to disable it, as the resulting graying doesn't look good. I have tried to approximate the effect by changing the new value back to its previous value when the user clicks the QCheckBox. However, the problem is compounded by the fact that the state of the widget is described by the "checked" properties of the QAbstractButton parent class, and the "state" properties of the QCheckBox class itself. This gives rise to a combinatorial exercise of signals and slots, of which I have been unable to obtain any good result.
var_ctrl = QtGui.QCheckBox( 'some name' )
def rdslot1(state):
if state == QtCore.Qt.Checked:
var_ctrl.setCheckState( QtCore.Qt.Unchecked )
else:
var_ctrl.setCheckState( QtCore.Qt.Checked )
def rdslot2(state):
if var_ctrl.isChecked():
var_ctrl.setChecked(False)
else:
var_ctrl.setChecked(True)
# Signal/Slot combinations (only one should be active)
var_ctrl.stateChanged.connect( rdslot1 )
var_ctrl.toggled.connect( rdslot2 )
var_ctrl.stateChanged.connect( rdslot2 )
var_ctrl.toggled.connect( rdslot1 )
I'm late to the party - it seems like you got a solution that works. For future reference tho, another way you can do it would be to consume the mouse events - which keeps all of your signals working the way they should:
from PyQt4 import QtGui, QtCore
class MyCheckBox(QtGui.QCheckBox):
def __init__( self, *args ):
super(MyCheckBox, self).__init__(*args) # will fail if passing **kwargs
self._readOnly = False
def isReadOnly( self ):
return self._readOnly
def mousePressEvent( self, event ):
if ( self.isReadOnly() ):
event.accept()
else:
super(MyCheckBox, self).mousePressEvent(event)
def mouseMoveEvent( self, event ):
if ( self.isReadOnly() ):
event.accept()
else:
super(MyCheckBox, self).mouseMoveEvent(event)
def mouseReleaseEvent( self, event ):
if ( self.isReadOnly() ):
event.accept()
else:
super(MyCheckBox, self).mouseReleaseEvent(event)
# Handle event in which the widget has focus and the spacebar is pressed.
def keyPressEvent( self, event ):
if ( self.isReadOnly() ):
event.accept()
else:
super(MyCheckBox, self).keyPressEvent(event)
#QtCore.pyqtSlot(bool)
def setReadOnly( self, state ):
self._readOnly = state
readOnly = QtCore.pyqtProperty(bool, isReadOnly, setReadOnly)
Setting the code up this way gets you a few things (which you may or may not care about) but can be useful when developing custom Qt widgets:
Consuming the event blocks the signal emission, so you can still connect other slots to things like clicked & toggled. If you're looking for those signals and then just switching the value on/off - then other widgets listening for those signals will be triggered incorrectly
Using isReadOnly/setReadOnly keeps the class following the Qt coding style
Creating pyqtSignals & pyqtSlots will help if you expose the plugin to Qt's Designer
Well later on I came up with a shortcut, which simply catches the clicks of the user and handles them according to a specifiable 'Modifiable' property. I have made this class:
class MyQCheckBox(QtGui.QCheckBox):
def __init__(self, *args, **kwargs):
QtGui.QCheckBox.__init__(self, *args, **kwargs)
self.is_modifiable = True
self.clicked.connect( self.value_change_slot )
def value_change_slot(self):
if self.isChecked():
self.setChecked(self.is_modifiable)
else:
self.setChecked(not self.is_modifiable)
def setModifiable(self, flag):
self.is_modifiable = flag
def isModifiable(self):
return self.is_modifiable
It behaves just like a normal QCheckBox, being modifiable by default. However, when you call setModifiable(False), everytime you click it, it keeps the current state of the widget. The trick was to catch the clicked signal, not toggled neither stateChanged.
Try to disable the checkbox widget, but override its look using widget palette or style