我正在尝试创建一个简单的 Pyqt 应用程序,它有一个按钮和一个 QVBoxLayout。当用户单击按钮时,我希望它添加一行(QHBoxlayout),其中包含多个小部件,例如 QLineedit、QLabel 和 QButton。这样用户就可以添加多行。一旦用户单击任何这些行,我希望整行更改其边框颜色,以便用户知道他站在哪一行。
我尝试以下代码:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QHBoxLayout, QLineEdit
from PyQt5 import QtCore
class MyHBoxLayout(QWidget):
selected = QtCore.pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.setStyleSheet("border: 1px solid black;")
self.line_edit = QLineEdit()
self.button = QPushButton('Remove')
self.layout.addWidget(self.line_edit)
self.layout.addWidget(self.button)
self.button.clicked.connect(self.removeMe)
self.line_edit.installEventFilter(self)
def removeMe(self):
self.setParent(None)
self.deleteLater()
def mousePressEvent(self, event):
self.selected.emit()
def eventFilter(self, source, event):
if source == self.line_edit and event.type() == QtCore.QEvent.MouseButtonPress:
self.selected.emit()
return super().eventFilter(source, event)
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Highlight Selected QHBoxLayout')
self.setGeometry(100, 100, 400, 300)
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
central_layout = QVBoxLayout()
central_widget.setLayout(central_layout)
add_button = QPushButton('Add QHBoxLayout', self)
central_layout.addWidget(add_button)
add_button.clicked.connect(self.addHBoxLayout)
self.container_widget = QWidget()
container_layout = QVBoxLayout()
self.container_widget.setLayout(container_layout)
central_layout.addWidget(self.container_widget)
self.selected_hbox = None
def addHBoxLayout(self):
hbox = MyHBoxLayout()
container_layout = self.container_widget.layout()
container_layout.addWidget(hbox)
hbox.selected.connect(self.onHBoxLayoutSelected)
def onHBoxLayoutSelected(self):
sender = self.sender()
if self.selected_hbox:
self.selected_hbox.setStyleSheet("border: 2px solid black;")
sender.setStyleSheet("border: 2px solid blue;")
self.selected_hbox = sender
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
但它会单独更改所有小部件的边框,而不是整行。
我该如何修复它?
(我有另一个错误,当我在删除一行后单击 QHBoxlayout 时,应用程序会崩溃,但此时我专注于边框颜色)。
谢谢你
首先,您没有绘制 QHBoxLayout 的边框,因为布局管理器是不是小部件,并且不绘制任何内容。您尝试做的是绘制 container 的边框,但是您的 QSS 语法是错误的,因为您正在设置一个通用属性,它将为目标小部件 及其子部件应用边框。
在容器或复杂的小部件上设置通用属性也是错误的,因为某些小部件需要设置特定的子控件属性,如果不这样做,您最终会得到基本的原始样式(基于“windows”样式) )这非常难看,并且当缺少属性时通常无法按预期正确绘制小部件。此外,您看不到容器的边框,因为默认情况下,QWidget 子类
不会绘制样式表设置的任何内容,除非正确实现paintEvent()
或设置
WA_StyledBackground
属性。首先要做的是在 QSS 中使用正确的
选择器类型,并按照上面的说明设置属性:
self.setStyleSheet("MyHBoxLayout { border: 1px solid black; }")
self.setAttribute(Qt.WA_StyledBackground)
上面将仅显示 MyHBoxLayout
实例的边框,而不是其子实例。然后,如果你想切换颜色,你需要使用相同的语法,但更好的方法是在
MyHBoxLayout
的专用函数中执行此操作,而不是在外部:
class MyHBoxLayout(QWidget):
...
def setSelected(self, selected):
color = 'blue' if selected else 'black'
self.setStyleSheet(
"MyHBoxLayout {{ border: 1px solid {}; }}".format(color))
class MyApp(QMainWindow):
...
def onHBoxLayoutSelected(self):
sender = self.sender()
if sender == self.selected_hbox:
# already selected, no need to go further
return
if self.selected_hbox:
self.selected_hbox.setSelected(False)
sender.setSelected(True)
self.selected_hbox = sender
请注意,仅删除小部件本身是不够的。您必须记住,PyQt 是围绕 C++ Qt 库的 Python 绑定,因此 Python 中使用的对象只是 Qt 对象的包装器,删除 Qt 对象并不会删除其 python 引用。
您的程序崩溃,因为 Qt 对象不再存在(在终端或提示符中运行您的程序,您将看到完整的错误),因此任何访问它的尝试都会导致内存访问错误。为了避免此类问题,您可以使用一个简单的
try/except
块:
if self.selected_hbox:
try:
self.selected_hbox.setSelected(False)
except RuntimeError:
pass
不过,更好的方法是在删除小部件之前使用进一步的信号:
class MyHBoxLayout(QWidget):
selected = pyqtSignal()
aboutToBeRemoved = pyqtSignal()
...
def removeMe(self):
self.aboutToBeRemoved.emit()
# no need to call `setParent(None)` if you're going to delete the widget
self.deleteLater()
class MyApp(QMainWindow):
...
def addHBoxLayout(self):
...
hbox.aboutToBeRemoved.connect(self.onHBoxRemove)
def onHBoxRemove(self):
if self.sender() is self.selected_hbox:
self.selected_hbox = None
请注意,上面的代码将显示比所需区域更大的边框,这是因为默认的 addWidget()
函数始终尝试使添加的小部件占据布局项中的所有可用空间。为了防止这种情况,您有两种选择:
alignment
参数:
container_layout.addWidget(hbox, alignment=Qt.AlignVCenter)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
最后,您只考虑鼠标单击来显示“聚焦”行,但这并不考虑键盘导航。执行您想要的操作的正确方法是检查
FocusIn
事件类型。