QWidget cannot catch ESCAPE, BACKSPACE or C-x key press events - python

I am catching key press events by overriding the KeyPressEvent method of my class, inheriting from QWidget, but I cannot see key press events for the ESCAPE and BACKSPACE keys, as well as C-x or again C-c. I can see key release events though. Who catches them ?
class KeyCaptureWidget(QWidget):
# init…
def keyPressEvent(self, event):
key = event.key()
logging.info("key press: {}".format(key))
def keyReleaseEvent(self, event):
key_code = event.key()
logging.info("key release: {}".format(key_code))
The difference between C-x and say C-b is the following:
pressing C-b prints: control is pressed, b is pressed, b is released, control is released.
pressing C-x prints: control is pressed, [nothing], x is released, control is released.
In my QWidget, I use a QVBoxLayout to which I put two QWebEngine views. I tried to override the key press method of the QWebEngineView, but they don't seem to catch anything (and this is the behaviour I expected).
def __init__(self):
self.qtwindow = KeyCaptureWidget()
self.layout = QVBoxLayout()
self.view1 = QWebEngineView() # or a custom one to override keyPressEvent
# self.view2 = the same
self.layout.addWidget(self.view1)
self.layout.addWidget(self.view2)
self.qtwindow.setLayout(self.layout)
self.qtwindow.show()
I tried to catch these with event filters on my QWidget class but again, no luck: I see only a key release for ESC or BACKSPACE.
def __init__(self):
super().__init__()
self.installEventFilter(self)
def eventFilter(self, source, event):
logging.info("event filter: {}".format(event))
if event.type() == QEvent.KeyPress:
logging.info(" event filter key PRESS")
if event.type() == QEvent.KeyRelease:
logging.info(" event filter key RELEASE")
return True
How can I catch them ? Thank you.

Events do not necessarily propagate among all widgets, if a widget consumes it then it will no longer propagate to the parent. In the case of the keyboard events will only be consumed first by the widget that has the focus, in your case QWebEngineView consumes them before and prevents it from being projected in other widgets. If you want to hear events from the keyboard of a window then you must use the QShortcuts, and for that you must create a QShortcut:
Ctrl + C: QtGui.QKeySequence("Ctrl+C")
Ctrl + X: QtGui.QKeySequence("Ctrl+X")
Esc: QtGui.QKeySequence("Escape")
Backspace: QtGui.QKeySequence("Backspace")
Considering the above, the solution is:
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.m_view1 = QtWebEngineWidgets.QWebEngineView()
self.m_view2 = QtWebEngineWidgets.QWebEngineView()
self.m_view1.load(QtCore.QUrl("https://stackoverflow.com/questions/56890831"))
self.m_view2.load(QtCore.QUrl("https://doc.qt.io/"))
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.m_view1)
lay.addWidget(self.m_view2)
QtWidgets.QShortcut(
QtGui.QKeySequence("Ctrl+C"), self, activated=self.on_Ctrl_C
)
QtWidgets.QShortcut(
QtGui.QKeySequence("Ctrl+X"), self, activated=self.on_Ctrl_X
)
QtWidgets.QShortcut(
QtGui.QKeySequence("Escape"), self, activated=self.on_Escape
)
QtWidgets.QShortcut(
QtGui.QKeySequence("Backspace"), self, activated=self.on_Backspace
)
#QtCore.pyqtSlot()
def on_Ctrl_C(self):
print("Ctrl+C")
#QtCore.pyqtSlot()
def on_Ctrl_X(self):
print("Ctrl+X")
#QtCore.pyqtSlot()
def on_Escape(self):
print("Escape")
#QtCore.pyqtSlot()
def on_Backspace(self):
print("Backspace")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Related

How to dim background when using qmessagebox?

I want to set the background in dim mode, when a QMessagebox is popped up.
Currently, I have tried to use a simple QMesssagebox, but the background shows as normal display, when it pops up.
The image for 1st page is as follow
When go to next slide is pushed, it goes to next index as follow
When going back to 1st index, the back button is pushed which pops up the messagebox as follow
However, the mainwindow seems to have no effect on its focus.
Therefore, what would I need to do to make it dimmer than the focused messagebox.
How can I do this? Any suggestions?
EDIT
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = uic.loadUi("message.ui",self)
self.notification = QMessageBox()
self.ui.next_slide.clicked.connect(self.second_index)
self.ui.go_back.clicked.connect(self.alert_msg)
self.show()
def home(self):
self.ui.stackedWidget.setCurrentIndex(0)
def second_index(self):
self.ui.stackedWidget.setCurrentIndex(1)
def alert_msg(self):
self.notification.setWindowTitle("Exiting")
self.notification.setText("Are you sure, you want to exit")
self.notification.setIcon(QMessageBox.Critical)
self.notification.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
self.back = self.notification.exec_()
if self.back == QMessageBox.Yes:
self.home()
else:
pass
if __name__ == "__main__":
app=QApplication(sys.argv)
mainwindow=MainWindow()
app.exec_()
You can create a custom widget that is a direct child of the window that has to be "dimmed", ensure that it always has the same size as that window, and just paint it with the selected color:
class Dimmer(QWidget):
def __init__(self, parent):
parent = parent.window()
super().__init__(parent)
parent.installEventFilter(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.adaptToParent()
self.show()
def adaptToParent(self):
self.setGeometry(self.parent().rect())
def eventFilter(self, obj, event):
if event.type() == event.Resize:
self.adaptToParent()
return super().eventFilter(obj, event)
def paintEvent(self, event):
qp = QPainter(self)
qp.fillRect(self.rect(), QColor(127, 127, 127, 127))
class MainWindow(QMainWindow):
# ...
def alert_msg(self):
dimmer = Dimmer(self)
# ...
self.back = self.notification.exec_()
dimmer.close()
Note that, unless you plan to reuse the "dim widget", it must be destroyed either by calling close() as done above (see the WA_DeleteOnClose flag) or using deleteLater(). Hiding it will not be enough.

Qt: Prevent drag-and-drop on child widgets and show forbidden cursor

I want to enable drag and drop functionality in some custom widgets, to allow reordering and rearranging the widgets within a layout. The basic functionality is working, but I'd like to prevent dropping a widget on itself, or on a child of itself. This is straightforward in the dropEvent method, but I can't find a way to also show the "forbidden" cursor while dragging, such that the user is aware that a drop will not be allowed.
The example below shows a test implementation, where the widgets "One" to "Five" can be dragged and dropped (no rearrangement will happen, only a message will be printed on the terminal, you may need to press Ctrl or something to initiate a drag). The problem line is the first ev.accept() in the dragEnterEvent method. By accepting the event, the cursor is shown in an "allowed" state (a grabbing hand for me). For example, trying to drag "One" and drop it onto "Three" appears as allowed, although nothing will happen. However, ignoring the event results in the event being propagated to the parent, so in that case dragging "Three" and dropping it onto "Three" results in "One" getting the drop instead. Setting the WA_NoMousePropagation attribute does not seem to make any difference.
So, what I would need is a way to "accept" the event, so that it will not be propagated to the parent, but still show a "forbidden" cursor, as if nobody accepted the event. Any ideas?
#!/usr/bin/env python3
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class WidgetMimeData(QtCore.QMimeData):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.itemObject = None
def hasFormat(self, mime):
if (self.itemObject and (mime == 'widgetitem')):
return True
return super().hasFormat(mime)
def setItem(self, obj):
self.itemObject = obj
def item(self):
return self.itemObject
class DraggableWidget(QGroupBox):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.setAcceptDrops(True)
def addWidget(self, widget):
return self.layout().addWidget(widget)
def mouseMoveEvent(self, ev):
pixmap = QPixmap(self.size())
pixmap.fill(QtCore.Qt.transparent)
painter = QPainter()
painter.begin(pixmap)
painter.setOpacity(0.8)
painter.drawPixmap(0, 0, self.grab())
painter.end()
drag = QDrag(self)
mimedata = WidgetMimeData()
mimedata.setItem(self)
drag.setMimeData(mimedata)
drag.setPixmap(pixmap)
drag.setHotSpot(ev.pos())
drag.exec_(QtCore.Qt.MoveAction)
def dragEnterEvent(self, ev):
item = ev.mimeData().item()
if item.isAncestorOf(self):
#ev.ignore()
ev.accept()
else:
ev.accept()
def dropEvent(self, ev):
item = ev.mimeData().item()
if not item.isAncestorOf(self):
print('dropped on', self.layout().itemAt(0).widget().text())
ev.accept()
class HelloWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
w1 = DraggableWidget()
w1.addWidget(QLabel('One'))
w2 = DraggableWidget()
w2.addWidget(QLabel('Two'))
w3 = DraggableWidget()
w3.addWidget(QLabel('Three'))
w4 = DraggableWidget()
w4.addWidget(QLabel('Four'))
w5 = DraggableWidget()
w5.addWidget(QLabel('Five'))
w1.addWidget(w3)
w1.addWidget(w4)
w2.addWidget(w5)
layout = QVBoxLayout()
layout.addWidget(w1)
layout.addWidget(w2)
layout.addStretch(1)
centralWidget = QWidget(self)
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = HelloWindow()
mainWin.show()
sys.exit( app.exec_() )
Probably the easiest way is to always accept the event in dragEnterEvent and in dragMoveEvent ignore it when the source of the event is an ancestor of self, i.e.
def dragEnterEvent(self, ev):
ev.accept()
def dragMoveEvent(self, ev):
item = ev.source()
if item.isAncestorOf(self):
ev.ignore()
By ignoring the event in dragMoveEvent you wouldn't need the check in dropEvent either and can simply do
def dropEvent(self, ev):
print('Dropped of', self.layout().itemAt(0).widget().text())
ev.accept()

How can I know when the Enter key was pressed on QTextEdit

I'm writing Chat gui for client on Python using PyQt5.
I have a QTextEdit, which the client can write messages in it.
I wan't to know when the 'Enter' key is being pressed while the focus is on the QTextEdit.
I tried using installEventFilter function but it detects keys being pressed on all of the other widgets but the QTextEdit one.
What can I do to fix that?
def initUI(self):
# ...
self.text_box = QtWidgets.QTextEdit(self)
self.installEventFilter(self)
# ...
def keyPressEvent(self, qKeyEvent):
print(qKeyEvent.key())
if qKeyEvent.key() == Qt.Key_Return:
if self.text_box.hasFocus():
print('Enter pressed')
When you override keyPressEvent you are listening to the events of the window, instead install an eventFilter to the QTextEdit, not to the window as you have done in your code, and check if the object passed as an argument is the QTextEdit:
def initUI(self):
# ...
self.text_box = QtWidgets.QTextEdit(self)
self.text_box.installEventFilter(self)
# ...
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress and obj is self.text_box:
if event.key() == QtCore.Qt.Key_Return and self.text_box.hasFocus():
print('Enter pressed')
return super().eventFilter(obj, event)
The answer from #eyllanesc is very good if you are determined to use QTextEdit.
If you can get away with QLineEdit and its limitations, you can use the returnPressed() signal. The biggest drawback for QLineEdit is you are limited to one line of text. And there is no word wrap. But the advantage is you don't have to mess with eventFilters or think too hard about how keyPress signals fall through all of the widgets in your app.
Here is a minimal example that copies from one QLineEdit to another:
import sys
from PyQt5.QtWidgets import *
class PrintWindow(QMainWindow):
def __init__(self):
super().__init__()
self.left=50
self.top=50
self.width=300
self.height=300
self.initUI()
def initUI(self):
self.setGeometry(self.left,self.top,self.width,self.height)
self.line_edit1 = QLineEdit(self)
self.line_edit1.move(50, 50)
self.line_edit1.returnPressed.connect(self.on_line_edit1_returnPressed)
self.line_edit2 = QLineEdit(self)
self.line_edit2.move(50, 100)
self.show()
def on_line_edit1_returnPressed(self):
self.line_edit2.setText(self.line_edit1.text())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = PrintWindow()
sys.exit(app.exec_())
In this example, I have manually connected to the signal in line 22 (self.line_edit1.returnPressed.connect). If you are using a ui file, this connection can be left out and your program will automatically call the on__returnPressed method.
When you override keyPressEvent you are listening to the events of the window, instead install an eventFilter to the QTextEdit, not to the window as you have done in your code, and check if the object passed as an argument is the QTextEdit:
def initUI(self):
# ...
self.text_box = QtWidgets.QTextEdit(self)
self.text_box.installEventFilter(self)
# ...
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress and obj is self.text_box:
if event.key() == QtCore.Qt.Key_Return and self.text_box.hasFocus():
print('Enter pressed')
return True
return False
This is building upon the answer of #eyllanesc and the problem #Daniel Segal faced. Adding the correct return values as such to the eventFilter solves the problem.

Get the RFID text without QLineEdit

I have an QLineEdit that will take the value of an RFID tag and the log the user in with the value that is received, I have setup the QLineEdit so that it calls a login function when the enter key is pressed.
The only issue that I am left with is that the QLineEdit is visible which is not necessary as the user will not be typing the value of their RFID tag, they will just scan it and the scanner will enter the value and press enter.
rfid_enter = QLineEdit()
rfid_enter.returnPressed.connect(lambda: log_user_in(rfid_enter.text()))
def log_user_in(value):
print(value) (THIS WILL LOG THE USER IN)
QLineEdit needs to have the focus to get the keyboard events, but to have the focus it needs to be visible, so hiding it will not be the solution.
As pointed out by the OP in the comments in the window there are only: two labels, and a few spacers that do not handle the keyboard event so there are no widgets that intercept that event so the window can get them without problems (if there are other widget like QLineEdits, QTextEdit, QSpinBox, etc. the logic could change).
Considering the above, I have implemented the following logic:
import string
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
returnPressed = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(
QtWidgets.QLabel("My Label", alignment=QtCore.Qt.AlignHCenter),
alignment=QtCore.Qt.AlignTop,
)
self.m_text = ""
self.returnPressed.connect(self.log_user_in)
def keyPressEvent(self, event):
if event.text() in string.ascii_letters + string.digits:
self.m_text += event.text()
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
self.returnPressed.emit(self.m_text)
# clear text
self.m_text = ""
super(Widget, self).keyPressEvent(event)
#QtCore.pyqtSlot(str)
def log_user_in(self, text):
print(text)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(240, 320)
w.show()
sys.exit(app.exec_())
The answer from eyllanesc would work if the entire window was the class that he had created, but in my case the layout will change and therefore I could not use that as the main window.
I went for a cheat approach of just trying to hide the box as much as possible and ended up with this result.
class LogInRFIDListener(QtWidgets.QPlainTextEdit):
def __init__(self):
super(LogInRFIDListener, self).__init__()
self.setTextInteractionFlags(QtCore.Qt.TextEditable)
self.setCursor(QtCore.Qt.ArrowCursor)
self.setStyleSheet("border: none; color: transparent;") # Hide the border around the text box
self.setCursorWidth(0) # Hide the cursor
def keyPressEvent(self, event): # Major restricting needed
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
log_user_in(self.toPlainText())
super(LogInRFIDListener, self).keyPressEvent(event)

Implementing keyPressEvent in QWidget

I have a QDialog window that has a continue button. The continue button is the default button because whenever I press the enter key, the continue button is pressed. I discovered something strange: when I press the enter key three times, the continue button presses three times. However, when I press it a fourth time, the whole window closes. I have a cancel button right below the continue button that closes the window, but I don't make the cancel button the default button or anything.
I wanted to override the keyPressEvent so that whenever I'm in the window, the enter button will always be connected to the continue button.
This is what I have right now:
class ManualBalanceUI(QtGui.QWidget):
keyPressed = QtCore.pyqtSignal()
def __init__(self, cls):
super(QtGui.QWidget, self).__init__()
self.window = QtGui.QDialog(None, QtCore.Qt.WindowSystemMenuHint)
self.ui = uic.loadUi('ManualBalanceUI.ui', self.window)
self.keyPressed.connect(self.on_key)
def keyPressEvent(self, event):
super(ManualBalanceUI, self).keyPressEvent(event)
self.keyPressed.emit(event)
def on_key(self, event):
if event.key() == QtCore.Qt.Key_Enter and self.ui.continueButton.isEnabled():
self.proceed() # this is called whenever the continue button is pressed
elif event.key() == QtCore.Qt.Key_Q:
self.window.close() # a test I implemented to see if pressing 'Q' would close the window
def proceed(self):
...
...
However, this doesn't seem to be doing anything right now. Pressing 'Q' doesn't close the window, and I can't really tell if the 'enter' key is working or not.
I looked at this question beforehand: PyQt Connect to KeyPressEvent
I also reviewed all the documentation on SourceForge. Any help would be greatly appreciated!
You can do two ways and one is simply re implement keyPressevent with out any fancy work. Like this
from PyQt4 import QtCore, QtGui
import sys
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.setGeometry(300, 300, 250, 150)
self.show()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Q:
print "Killing"
self.deleteLater()
elif event.key() == QtCore.Qt.Key_Enter:
self.proceed()
event.accept()
def proceed(self):
print "Call Enter Key"
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Or as you tried with signals, in your case you where missing to implement this signal properly, here is updated version.
class Example(QtGui.QWidget):
keyPressed = QtCore.pyqtSignal(QtCore.QEvent)
def __init__(self):
super(Example, self).__init__()
self.setGeometry(300, 300, 250, 150)
self.show()
self.keyPressed.connect(self.on_key)
def keyPressEvent(self, event):
super(Example, self).keyPressEvent(event)
self.keyPressed.emit(event)
def on_key(self, event):
if event.key() == QtCore.Qt.Key_Enter and self.ui.continueButton.isEnabled():
self.proceed() # this is called whenever the continue button is pressed
elif event.key() == QtCore.Qt.Key_Q:
print "Killing"
self.deleteLater() # a test I implemented to see if pressing 'Q' would close the window
def proceed(self):
print "Call Enter Key"
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If you're looking at this in 2019, and If Achayan's method does not work for you then check a couple things :
1) Is the keyPressEvent overridden in a child of the main window or main widget? The child's function will override the methods on the parent (at least when using a QMainWindow with setCentralWidget). If this is the case, then the signal might not be emitted like you expect
2) Do you have a widget that handles key inputs differently than QWidget? For example, if the focus is in a QTextEdit object, then key inputs will not be sent to keyPressEvent. Shift focus to another widget
For example, observe the output of the following :
class myDialog(QtWidgets.QDialog):
keyPressed = QtCore.pyqtSignal(QtCore.QEvent)
def __init__(self, parent=None):
super(myDialog, self).__init__(parent)
self.keyPressed.connect(self.on_key)
leftGroupBox = QtWidgets.QGroupBox('A Group Label')
text = QtWidgets.QTextEdit('Enter some text')
layout = QtWidgets.QVBoxLayout()
layout.addWidget(text)
leftGroupBox.setLayout(layout)
rightGroupBox = QtWidgets.QGroupBox('Label Options')
label1 = QtWidgets.QCheckBox('ahu')
layout = QtWidgets.QVBoxLayout()
layout.addWidget(label1)
rightGroupBox.setLayout(layout)
# Create the main layout
mainLayout = QtWidgets.QGridLayout()
mainLayout.addWidget(leftGroupBox)
mainLayout.addWidget(rightGroupBox)
self.setLayout(mainLayout)
def keyPressEvent(self, event):
# keyPressEvent defined in child
print('pressed from myDialog: ', event.key())
# self.keyPressed.emit(event) # Emit is hidden in child
def on_key(self, event):
print('event received # myDialog')
if event.key() == QtCore.Qt.Key_0:
print(0)
class MainWindow(QtWidgets.QMainWindow):
keyPressed = QtCore.pyqtSignal(QtCore.QEvent)
def __init__(self):
super(MainWindow, self).__init__()
self.keyPressed.connect(self.on_key)
self.setCentralWidget(myDialog())
self.show()
def keyPressEvent(self, event):
super(MainWindow, self).keyPressEvent(event)
print('pressed from MainWindow: ', event.key())
self.keyPressed.emit(event)
def on_key(self, event):
print('event received # MainWindow')
if event.key() == QtCore.Qt.Key_0:
print(0)
if __name__ == '__main__':
ex = MainWindow()
Output # console : (no event is received #myDialog OR MainWindow)
pressed from myDialog: 48
For Achayan's answer, I succeeded with the code.
It may be the enter key and the return key.
Try Key_Enter and Key_Return. They are different on my keyboard.
QtCore.QObject.connect(self.ui.search_box, QtCore.SIGNAL("textChanged()"), self.fucn1)
Whenever text is changed in search_box function fucn1 is called.
Works for pyqt4

Categories