I am trying to capture all of key pressed in my program
def keyPressEvent(self, event):
event = event.key()
if (event == QtCore.Qt.Key_Q):
print ('Q!.')
That function works fine when i am trying to capture keys in my window. (in this case Q_Key)
But if the key was pressed in a Text Widget (for example in a: QListView, QlistWidget, QLineEdit, and so many more ) it don't work. The function prints nothing. I am doing something wrong...
What can i do to fix it?
You will need to install an event-filter on the application object to get all key-press events:
class Window(QMainWindow):
def __init__(self):
...
QApplication.instance().installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.KeyPress:
print('KeyPress: %s [%r]' % (event.key(), source))
return super().eventFilter(source, event)
Related
I'm trying to write a PyQt5 GUI that captures all keyboard keys that are currently being pressed. Based on this answer, I've tried the following minimal code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import QEvent
class MainWindow(QWidget):
def __init__(self):
super().__init__()
QApplication.instance().installEventFilter(self)
self.pressedKeys = []
def eventFilter(self, source, event):
if event.type() == QEvent.KeyPress:
if int(event.key()) not in self.pressedKeys:
self.pressedKeys.append(int(event.key()))
print(self.pressedKeys)
elif event.type() == QEvent.KeyRelease:
if int(event.key()) in self.pressedKeys:
self.pressedKeys.remove(int(event.key()))
print(self.pressedKeys)
return super().eventFilter(source, event)
if __name__ == "__main__":
app = QApplication(sys.argv)
demo = MainWindow()
demo.show()
sys.exit(app.exec_())
When I run this, if I hold down a key the output list keeps flipping back and forth between one containing the key value and being empty. Similarly, holding down multiple keys adds the keys to the list, but alternates back and forth between containing and removing the final key that I have pressed. It seems that if I hold down keys the KeyRelease event still keeps getting triggered for the last key I pressed.
Is there are way to hold all current key presses in PyQt5, or should I use a different package (e.g., using one or other of the packages suggested in this question)?
Note, I've also tried:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.pressedKeys = []
def keyPressEvent(self, event):
if int(event.key()) not in self.pressedKeys:
self.pressedKeys.append(int(event.key()))
print(self.pressedKeys)
def keyReleaseEvent(self, event):
if int(event.key()) in self.pressedKeys:
self.pressedKeys.remove(int(event.key()))
print(self.pressedKeys)
if __name__ == "__main__":
app = QApplication(sys.argv)
demo = MainWindow()
demo.show()
sys.exit(app.exec_())
which results in pretty much the same behaviour.
Physical keyboards have the "autorepeat" feature, which allows simulating multiple pressure of a key while keeping it pressed.
This results in having multiple press/release events while a standard key (usually, not modifiers) is down and until it's physically released, with a rate normally set for the system.
You can check if the event is actually a physical press/release or if it isAutoRepeat():
def eventFilter(self, source, event):
if event.type() == QEvent.KeyPress:
if not event.isAutoRepeat() and int(event.key()) not in self.pressedKeys:
self.pressedKeys.append(int(event.key()))
print(self.pressedKeys)
elif event.type() == QEvent.KeyRelease:
if not event.isAutoRepeat() and int(event.key()) in self.pressedKeys:
self.pressedKeys.remove(int(event.key()))
print(self.pressedKeys)
return super().eventFilter(source, event)
I have a QMainWindow application that has multiple widgets (buttons, labels, etc.) inside it.
How can I get an event when the user presses ANYWHERE of the app?
I tried to customize mousePressEvent() function, but this doesn't accept the event when other widgets (buttons, labels, etc.) are pressed.
Explanation:
The handling of mouse events between the widgets goes from children to parents, that is, if the child does not accept the event (it does not use it) then it will pass the event to the parent. For example, if you press on the QPushButton, it accepts the event and the parent is not notified, unlike QLabel that does not consume it, so the event passes to the parent.
Tools:
On the other hand, there are several methods to listen to events in general, such as:
Override any method like mousePressEvent, keyPressEvent, etc or the event or customEvent method.
Use an eventFilter.
Solution:
An alternative is to apply some previous method to all the widgets but you can discard all of them for the current objective, for example:
In the first and second method it would involve detecting when a widget is added or removed (using QEvent::ChildAdded or QEvent::ChildRemoved).
In the first method it would imply override the methods that many times is impossible.
With the above, the problem is attacked on the widgets side, but there are other alternatives:
Override the notify() method of Q{Core, GUi,}Application, verify that it is a widget and that it belongs to the window, it also implies discriminating if the event has already been consumed.
Listen to the mouse event associated with the window (QWindow).
In this case the most reasonable is the second method.
import sys
from PyQt5 import QtCore, QtWidgets
class MouseObserver(QtCore.QObject):
pressed = QtCore.pyqtSignal(QtCore.QPoint)
released = QtCore.pyqtSignal(QtCore.QPoint)
moved = QtCore.pyqtSignal(QtCore.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() == QtCore.QEvent.MouseButtonPress:
self.pressed.emit(event.pos())
elif event.type() == QtCore.QEvent.MouseMove:
self.moved.emit(event.pos())
elif event.type() == QtCore.QEvent.MouseButtonRelease:
self.released.emit(event.pos())
return super().eventFilter(obj, event)
class MainWindow(QtWidgets.QMainWindow):
pass
def main(args):
app = QtWidgets.QApplication(args)
w = MainWindow()
w.show()
mouse_observer = MouseObserver(w.window().windowHandle())
mouse_observer.pressed.connect(lambda pos: print(f"pressed: {pos}"))
mouse_observer.released.connect(lambda pos: print(f"released: {pos}"))
mouse_observer.moved.connect(lambda pos: print(f"moved: {pos}"))
app.exec_()
if __name__ == "__main__":
main(sys.argv)
I try to find a way to catch event when user move Qdialog from titleBar.
My goal is to attach a drag event to dock my custom qdialog inside my mainWindow. (on Linux)
In other terms, do what dockwidgets do (I can use dockwidget) I have to do the same with Custom Qdialog (or widget with Qt.Window flags)
I see in c++ Qt source code than for QDockWidget, They use this kind of stuff:
bool QDockWidget::event(QEvent *event)
{ [...]
case QEvent::NonClientAreaMouseMove:
case QEvent::NonClientAreaMouseButtonPress:
case QEvent::NonClientAreaMouseButtonRelease:
case QEvent::NonClientAreaMouseButtonDblClick:
d->nonClientAreaMouseEvent(static_cast<QMouseEvent*>(event));
But when I try to catch this kind of event on pyside, I recieve nothin:
def event(self, e):
print('event %s' % e.type())
return super(myDyalig,self).event(e)
event PySide2.QtCore.QEvent.Type.ActivationChange
event PySide2.QtCore.QEvent.Type.UpdateRequest
event PySide2.QtCore.QEvent.Type.Paint
# I recieve only this move event when user stop moving (when he
# release the button)
event PySide2.QtCore.QEvent.Type.Move
event PySide2.QtCore.QEvent.Type.WindowActivate
event PySide2.QtCore.QEvent.Type.ActivationChange
event PySide2.QtCore.QEvent.Type.UpdateRequest
event PySide2.QtCore.QEvent.Type.Paint
Any idea how to do this ? (or another idea how to realize a drag event with qdialog)
Edit:
a minimal example:
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class CustomDialog(QDialog):
def __init__(self, parent=None):
super(CustomDialog,self).__init__(parent)
self.setFixedSize(QSize(200,200))
def event(self, e):
print('event %s' % e.type())
return super(CustomDialog,self).event(e)
def main():
import sys
app = QApplication(sys.argv)
dial = CustomDialog()
dial.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You need to install an event filter:
def __init__(self, parent=None):
super().__init__(parent)
#...
self.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() in (QEvent.NonClientAreaMouseButtonPress, QEvent.NonClientAreaMouseButtonPress, QEvent.Move):
print(event)
return super().eventFilter(obj, event)
See also: https://doc.qt.io/qt-5/eventsandfilters.html
Due to an astronomically atrocious bug on PyQt4, I need to fire a mousePressEvent artificially on a QWebView as a last breath of hope. For some obscure reason, QWebView's linkClicked and urlChanged signals do not pass a QUrl when the clicked link has Javascript involved (It does not work on Youtube videos, for example) and when the link was clicked with the left mouse button. The QUrl can be perfectly accessed when the link was clicked with the middle and right buttons.
class CQWebView(QtWebKit.QWebView):
def mousePressEvent(self, event):
if type(event) == QtGui.QMouseEvent:
if event.button() == QtCore.Qt.LeftButton:
# FIRE A QtCore.Qt.MiddleButton EVENT HERE,
# SO THAT I CAN GET THE BLOODY LINK AFTERWARDS.
elif event.button() == QtCore.Qt.MiddleButton:
self.emit(QtCore.SIGNAL("OPEN_IN_NEW_TAB")) # Example
elif event.button() == QtCore.Qt.RightButton:
self.emit(QtCore.SIGNAL("XXX")) # Example
So, I literally want to "click artificially", click without clicking, just trigger the event of clicking with the middle right button on a link, so that I can catch the QUrl correctly.
You can do it using the QtTest module as well. Set an event filter on your web view and check for left click mouse events and send a middle mouse click
def __init__(...)
self.ui_web_view.installEventFilter(self)
def eventFilter(self, obj, event):
if obj == self.ui_web_view:
if event.type() == QtCore.QEvent.MouseButtonPress:
if event.button() == QtCore.Qt.LeftButton:
print 'Handled'
QtTest.QTest.mouseClick(self.ui_web_view, QtCore.Qt.MiddleButton, QtCore.Qt.NoModifier, event.pos())
return True
return False
I've managed to click with the middle button by clicking with the left button using the sendEvent method from QtGui.QApplication.
class CQWebView(QtWebKit.QWebView):
app = None
def __init__(self, app):
QtWebKit.QWebView.__init__(self)
CQWebView.app = app
def mousePressEvent(self, event):
if type(event) == QtGui.QMouseEvent:
if event.button() == QtCore.Qt.LeftButton:
CQWebView.app.sendEvent(self, QtGui.QMouseEvent(event.type(), event.pos(), QtCore.Qt.MiddleButton, event.buttons(), event.modifiers()))
elif event.button() == QtCore.Qt.MiddleButton:
self.emit(QtCore.SIGNAL("linkClicked(const QUrl&)")) # Problem
elif event.button() == QtCore.Qt.RightButton:
self.emit(QtCore.SIGNAL("XXX")) # Example
I just needed to pass the instance of the QApplication to the instance of the custom QWebView on instantiation:
def compose_tab(self, index):
self.tabs[index].append(CQWebView(self))
This way, I click on the QWebView with the left mouse button and I click artificially on it with the middle button. The problem is that doing so I'm overriding the default behavior of the linkClicked and urlChanged signals from the QWebView. Even though I can emit these signals, I can't have access to the address of the link that was clicked, since by accessing QWebView.url() only gives me the present QUrl, and not the future clicked one.
Really frustrating.
I have developed a gui in Qt Designer, that among other things, displays a QTextEdit widget.
I need to handle the user entering the certain key combinations (TAB key, SHIFT + ENTER and SHIFT + RETURN so far) in such a way that it doesn't display those characters in the QTextEdit widget and implements some custom functionality.
I have created a handler to detect keyReleaseEvents, but that seems to be too late to prevent tabs, carriage returns etc. getting displayed, so then I tried handling keyPressEvents, but that doesn't seem to work at all. From what I've read online, people are saying that the keyPressEvent is getting consumed by the widget and so never gets propagated up to the gui. I don't know enough about QTextEdit widgets to say if that's correct, but for now I'm assuming it is.
So then I tried this in the class __init__ method for the gui (I have left out the check for the RETURN key to keep the code short below:
self.textEdit.event = self.eventX
Here's the code for the eventX method:
def eventX(self, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
print 'CODE WINDOW: Tab pressed'
self.textEdit.insertPlainText(QString('| |'))
self.textEdit.update()
event.accept()
return True
elif event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Enter) and event.modifiers() & Qt.ShiftModifier:
print 'CODE WINDOW: Shift pressed with Enter'
self.submitCode()
self.textEdit.update()
event.accept()
return True
else:
event.ignore()
return QWidget.event(self, event)
This is where things get strange. The code above will sort of work if I have specified that:
verticalScrollBarPolicy = Qt.ScrollBarAsNeeded
for the QTextEdit widget, except that it won't actually display the characters in the widget, but it does seem to handle the TAB and SHIFT-ENTER/SHIFT-RETURN keys being pressed?
And then, if I set verticalScrollBarPolicy = Qt.ScrollBarAlwaysOn, the eventX method seems to get completely ignored!
This is driving me crazy at this stage. Does anyone know what I'm doing wrong?
Thanks.
UPDATE: Based on the information from MDURANT, I now have the code below that works for me:
def handleEditorKeyPress(self, event):
if event.key() == Qt.Key_Tab:
# My custom code goes here.
event.accept()
return
elif event.key() == Qt.Key_Enter and event.modifiers() & Qt.ShiftModifier:
# My custom code goes here.
event.accept()
return
elif event.key() == Qt.Key_Return and event.modifiers() & Qt.ShiftModifier:
# My custom code goes here.
event.accept()
return
else:
event.ignore()
return QTextEdit.keyPressEvent(self.textEdit, event)
An example of capturing key events. In this case, I simply pass the event through to another identical widget, but in general, you can set the event function to anything
class win(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self, parent=None)
self.box = QtGui.QHBoxLayout(self)
self.setLayout(self.box)
self.ed1 = QtGui.QTextEdit(self)
self.ed2 = QtGui.QTextEdit(self)
self.ed1.keyPressEvent = self.ed2.keyPressEvent
self.ed1.keyReleaseEvent = self.ed2.keyReleaseEvent
self.box.addWidget(self.ed1)
self.box.addWidget(self.ed2)
See the documentation of the event: http://qt-project.org/doc/qt-4.8/qtextedit.html#keyPressEvent