Issue
I'm developing a GUI, and I have the issue that sometimes, hitting the 'enter' key makes several widgets send their signal. The weirdest part is that sometimes it happens, and sometimes not. The main thing is, I can't guarantee the focus on one and only one QGroupBox
at all times.
Here is a somewhat minimal example. If you run it and enter text, then hit 'enter', two functions will be executed (image below).
# -*- coding: utf-8 -*-
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QComboBox, QStyleFactory, QDialog, QTextEdit,
QGroupBox, QLabel, QLineEdit, QGridLayout, QPushButton, QVBoxLayout)
import sys
class GrblGUI(QDialog):
class PositionDescriber:
""" Label and widget associated for each axis. Save some writing later """
def __init__(self, labelText, initVal=0.0):
self.posLabel = QLabel(labelText)
self.value = initVal
self.posWidget = QLineEdit(str(self.value))
def __init__(self, parent=None):
""" Initializes the GUI and all widgets within.
Creates the general layout
"""
super(GrblGUI, self).__init__(parent)
self.originalPalette = QApplication.palette()
self.axes = [ self.PositionDescriber("X pos : "),
self.PositionDescriber("Y pos : "),
self.PositionDescriber("Z pos : "),
self.PositionDescriber("A pos : "),
self.PositionDescriber("B pos : ")]
self.size = range(len(self.axes))
self.ports = ["None"]
# Creating widget within panels
self.createConnectToCOM()
self.createPositionControlPanel()
self.createPushButtonsPanel()
self.createMessageHistory()
mainLayout = QGridLayout()
mainLayout.addWidget(self.connectToCOM, 0, 0, 1, 2)
mainLayout.addWidget(self.positionControlPanel, 1, 0)
mainLayout.addWidget(self.pushButtonsPanel, 0, 2, 2, 1)
mainLayout.addWidget(self.messageHistory, 1, 1)
mainLayout.setRowStretch(1, 1)
self.setLayout(mainLayout)
self.setWindowTitle("minimal")
QApplication.setStyle(QStyleFactory.create('Fusion'))
QApplication.setPalette(QApplication.style().standardPalette())
"""
Creation of panels, widgets, and associated layouts
"""
def createConnectToCOM(self):
self.connectToCOM = QGroupBox()
self.availableDevicesScroll = QComboBox()
for item in self.ports:
self.availableDevicesScroll.addItem(item)
connectLabel = QLabel("Connect to device :")
self.updatePushButton = QPushButton("Update")
self.updatePushButton.setDefault(True)
self.connectPushButton = QPushButton("Connect")
self.connectPushButton.setDefault(True)
self.updatePushButton.clicked.connect(self.updateAvailableCOM)
self.connectPushButton.clicked.connect(self.connectToPort)
layout = QGridLayout()
layout.addWidget(connectLabel, 0, 0)
layout.addWidget(self.availableDevicesScroll, 1, 0)
layout.addWidget(self.updatePushButton, 0, 1)
layout.addWidget(self.connectPushButton, 1, 1)
layout.setColumnStretch(0, 1)
self.connectToCOM.setLayout(layout)
def createPositionControlPanel(self):
self.positionControlPanel = QGroupBox("Position Control Panel : ")
for i in self.size:
self.axes[i].posWidget.returnPressed.connect(self.registerInput)
layout = QGridLayout()
for i in self.size:
layout.addWidget(self.axes[i].posLabel, i, 0)
for i in self.size:
layout.addWidget(self.axes[i].posWidget, i, 1)
sendPushButton = QPushButton("Send to pos")
sendPushButton.setDefault(True)
sendPushButton.clicked.connect(self.sendToPos)
layout.addWidget(sendPushButton, len(self.axes), 2, 1, 2)
layout.setRowStretch(6, 1)
self.positionControlPanel.setLayout(layout)
def createPushButtonsPanel(self):
self.pushButtonsPanel = QGroupBox("Things you may want to do : ")
self.homingPushButton = QPushButton("Homing")
self.homingPushButton.setDefault(True)
self.homingPushButton.clicked.connect(self.homing)
recPosPushButton = QPushButton("Record current pos")
recPosPushButton.setDefault(True)
recPosPushButton.clicked.connect(self.recordPosition)
layout = QVBoxLayout()
layout.setSpacing(20)
layout.addWidget(self.homingPushButton)
layout.addWidget(recPosPushButton)
layout.addStretch(1)
self.pushButtonsPanel.setLayout(layout)
def createMessageHistory(self):
self.messageHistory = QGroupBox("Message history : ")
self.textEdit = QTextEdit()
self.textEdit.setReadOnly(True)
self.textEdit.setPlainText("")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
self.messageHistory.setLayout(layout)
"""
Methods to call
"""
def connectToPort(self):
self.textEdit.append("connectToPort")
def updateAvailableCOM(self):
self.textEdit.append("updateAvailableCOM")
def registerInput(self):
self.textEdit.append("registerInput")
def homing(self):
self.textEdit.append("homing")
def recordPosition(self):
self.textEdit.append("recordPosition")
def sendToPos(self):
self.textEdit.append("sendToPos")
if __name__ == '__main__':
app = QApplication(sys.argv)
gallery = GrblGUI()
gallery.show()
app.exec()
# sys.exit(appctxt.app.exec())
And the result after entering text:
I've tried different thing such as setFocusPolicy(Qt.NoFocus)
or setFocus()
, but it didn't work. Any ideas?
Solution
The problem comes from both the default
and autoDefault
properties of QPushButton in combination with the usage of QDialog, and it has the following results
- if
autoDefault
isTrue
, a button becomes a possibledefault
button; - the
autoDefault
property of a QPushButton isFalse
, unless it is/becomes a child (even indirect) of a QDialog;
Events received by a widget that does not accept them are automatically propagated to its parent, up in the parenthood hierarchy, until one widget does accept it or the top level widget is reached.
QLineEdit by default handles the return key press, but does not accept it, which means that it knows that the key has been pressed (since it can emit the returnPressed
signal) but will not handle, thus propagating it to the parent.
Considering the above, the unwanted behavior is that the key event is also received by the QDialog, so there are various possibilities, depending on the requirements.
Use QWidget instead of QDialog
This is the easiest choice, but you might still need a QDialog for its features: the exec
event loop, the accepted/rejected
signals and result interface, or just to simplify the modality
Set the default
property to False
for all buttons
Obviously, you can set the autoDefault
property to False
for each button, but an easier solution is to use a cycle that loops over all QPushButton instances, which should be implemented in an override that we know for sure that would be called, like exec
or, maybe better, showEvent
:
class Dialog(QtWidgets.QDialog)
# ...
def showEvent(self, event):
super().showEvent(event)
if not event.spontaneous():
for btn in self.findChildren(QtWidgets.QPushButton):
if btn.default():
btn.setDefault(False)
if btn.autoDefault():
btn.setAutoDefault(False)
This can become a problem, though, as sometimes you might want to use that feature anyway: for instance, if you have a tab/stacked widget, and you want to avoid the return feature in a page that has a line edit, but not in another one that only has one button (like a wizard).
Ignore the Return
key in the dialog
Overriding the keyPressEvent
, and call the base implementation only if the key is not return or enter (this has the same problem above, as it completely disable the feature):
class Dialog(QtWidgets.QDialog)
# ...
def keyPressEvent(self, event):
if event.key() not in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
super().keyPressEvent(event)
Accept the key event in the line edit
This is probably a more appropriate approach, as it solves the problem at the source: consider the event as accepted in the QLineEdit if the return/enter key is pressed. This can only be done in a subclass:
class LineEdit(QtWidgets.QLineEdit):
def keyPressEvent(self, event):
super().keyPressEvent(event)
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
event.accept()
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.