你如何用 PyQt5 在列(或行)之间画线?
How do you draw lines between columns (or rows) with PyQt5?
我一直在努力学习 PyQt5 来重新实现我用 Tkinter 做的应用程序,但有一些设计差异。由于它不是一个复杂的应用程序,我想让它具有类似于 GitHub 桌面上的这个小 window 的样式(window 左侧的选项,其余的在剩下的 space):
我知道我的颜色现在看起来不太好,但我可以稍后处理。但是,我还没有找到如何绘制与这些相似的 lines/boxes,或者至少在我的 columns/rows.
之间的划分中
这是我目前的情况:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QWidget, QFileDialog, QGridLayout, QFrame
from PyQt5 import QtGui, QtCore
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Some Window Title')
self.app = QApplication(sys.argv)
self.screen = self.app.primaryScreen()
self.screen_size = self.screen.size()
self.screen_width = self.screen_size.width()
self.screen_height = self.screen_size.height()
self.setGeometry(
self.screen_width * 0.1,
self.screen_height * 0.1,
self.screen_width * 0.8,
self.screen_height * 0.8
)
self.setStyleSheet('background: #020a19;')
self.grid = QGridLayout()
self.grid.setVerticalSpacing(0)
self.grid.setContentsMargins(0, 0, 0, 0)
self.option_button_stylesheet = '''
QPushButton {
background: #020a19;
color: #c5cad4;
border-color: #c5cad4;
border: 0px 0px 0px 0px;
border-style: outset;
border-radius: 5px;
font-size: 15px;
font-weight: bold;
padding: 10px;
margin: 15px;
width: 2px;
}
QPushButton:hover {
background-color: #00384d;
}
'''
self.placeholder_button_stylesheet = '''
QPushButton {
background: #020a19;
color: #c5cad4;
border-top: none;
border-right: 1px;
border-left:none;
border-bottom: none;
border-color: #c5cad4;
border-style: outset;
padding: 10px;
margin: 0px;
width: 2px;
height: 100%;
}
QPushButton:hover {
background-color: #020a19;
}
'''
self.header_label = QLabel('Some Application')
self.option_1_button = QPushButton('Option 1')
self.option_2_button = QPushButton('Option 2')
self.option_3_button = QPushButton('Option 3')
self.header_label.setStyleSheet(
'''
font-size: 25px;
color: #c5cad4;
padding-left: 10px;
padding-top: 16px;
padding-bottom: 20px;
height: 10%;
border-bottom: 1px solid #c5cad4;
'''
)
self.header_label.setFixedHeight(120)
self.grid.addWidget(self.header_label, 0, 0, 1, 2)
self.option_1_button.setStyleSheet(self.option_button_stylesheet)
self.option_1_button.setFixedWidth(200)
self.option_1_button.setFixedHeight(100)
self.grid.addWidget(self.option_1_button, 1, 0)
self.option_2_button.setStyleSheet(self.option_button_stylesheet)
self.option_2_button.setFixedWidth(200)
self.option_2_button.setFixedHeight(100)
self.grid.addWidget(self.option_2_button, 2, 0)
self.option_3_button.setStyleSheet(self.option_button_stylesheet)
self.option_3_button.setFixedWidth(200)
self.option_3_button.setFixedHeight(100)
self.grid.addWidget(self.option_3_button, 3, 0)
self.grid.setRowStretch(4, 1)
self.initUI()
self.setLayout(self.grid)
self.show()
def initUI(self):
self.greet_text = QLabel('Welcome')
self.greet_text.setAlignment(QtCore.Qt.AlignCenter)
self.greet_text.setStyleSheet(
"""
font-size: 35px;
color: #c5cad4;
"""
)
self.grid.addWidget(self.greet_text, 1, 1, 5, 1)
def run():
window = Window()
sys.exit(window.app.exec())
run()
如您所见,我使用 QWidget
作为我的 window 元素。我知道我可以使用 QMainWindow
,但这改变了小部件的放置方式,我发现它更易于使用 QWidget
。我也不需要这个应用程序的工具栏或类似的东西。
我怎样才能画出这些线?
Qt style sheets (QSS) 不提供这样的功能,因为它只能 style 特定的小部件,而不能考虑它们在其中的位置布局。这对您的情况很重要,因为您要做的是在布局项之间绘制“分隔线”。
理论上可以通过为容器小部件设置背景来实现这一点,该背景将成为线条颜色,让所有 child 小部件绘制其 完整 内容使用 不透明 颜色,并确保布局始终具有等于线条宽度的间距,但如果内部小部件不符合其完整尺寸,则它们使用 alpha 通道,或者添加一些拉伸或进一步的间距,结果会很难看。
一种可能性是使用 QWidget 子类,覆盖其 paintEvent()
并使用 QPainter 绘制这些线条。
我们的想法是循环遍历所有布局项目,并在“当前”项目和前一个项目之间画线。
在下面的示例中,我创建了一个实现上述概念的基本 QWidget 子类,具体取决于所使用的布局。
请注意,我必须对您的原始代码进行一些更改和更正:
- 正如评论中已经指出的那样,现有的 QApplication 是强制性的以允许创建 QWidget,虽然可以使它成为 object 的属性(before 调用
super().__init__()
), 概念上还是错误的;
- 网格布局中的高度层次结构不应将单独的行和列用于其直接 child object,但应添加适当的 sub-layouts 或 child 小部件反而;在您的情况下,应该只有两行和两列:header 第一行有一个 2-column-span,菜单将位于第二行(索引 1)和第一列,第二列的右侧,菜单按钮将有自己的布局;
- 非常不鼓励为 parent 小部件设置通用样式 sheet 属性,因为复杂的小部件(例如 QComboBox、QScrollBar 和滚动区域 children)需要 all 属性设置为正常工作;对于 parent 或应用程序,应始终避免使用
setStyleSheet('background: ...')
;
- 样式 sheet 应在 parent 或应用程序上设置在许多小部件之间共享,并且应始终使用适当的 selectors;
- QSS
width
属性 应谨慎使用,因为它可能会使小部件部分不可见且无法使用;
- 如果您不想要任何边框,只需使用
border: none;
;
- 样式 sheet 尺寸仅支持绝对单位(参见
Length
属性 类型),忽略百分比值;
- 设置固定的高度、边距和边距可能会导致意外行为;确保您仔细阅读 box model 并进行一些测试以了解其行为;
- 类 不应在构造期间自动显示自己,因此
show()
不应在 __init__()
内调用(这不是特别“禁止”或不鼓励,但它仍然很好实践);
- 应始终使用
if __name__ == '__main__':
块,尤其是在处理依赖于事件循环的程序或工具包时(像所有 UI 框架,如 Qt);
这里是你的原始代码的重写:
class LayoutLineWidget(QWidget):
_borderColor = QColor('#c5cad4')
def paintEvent(self, event):
# QWidget subclasses *must* do this to properly use style sheets;
# (see doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget)
opt = QStyleOption()
opt.initFrom(self)
qp = QStylePainter(self)
qp.drawPrimitive(QStyle.PE_Widget, opt)
# end of default painting
layout = self.layout()
if not layout or layout.count() <= 1:
return
if layout.spacing() < 1:
layout.setSpacing(1)
return
qp.setPen(self._borderColor)
if isinstance(layout, QBoxLayout):
lastGeo = layout.itemAt(0).geometry()
if isinstance(layout, QVBoxLayout):
for row in range(1, layout.count()):
newGeo = layout.itemAt(row).geometry()
y = (lastGeo.bottom()
+ (newGeo.y() - lastGeo.bottom()) // 2)
qp.drawLine(0, y, self.width(), y)
lastGeo = newGeo
else:
for col in range(1, layout.count()):
newGeo = layout.itemAt(row).geometry()
x = (lastGeo.right()
+ (newGeo.x() - lastGeo.right()) // 2)
qp.drawLine(x, 0, x, self.height())
lastGeo = newGeo
elif isinstance(layout, QGridLayout):
for i in range(layout.count()):
row, col, rowSpan, colSpan = layout.getItemPosition(i)
if not row and not col:
continue
cellRect = layout.cellRect(row, col)
if rowSpan:
cellRect |= layout.cellRect(row + rowSpan - 1, col)
if colSpan:
cellRect |= layout.cellRect(row, col + colSpan - 1)
if row:
aboveCell = layout.cellRect(row - 1, col)
y = (aboveCell.bottom()
+ (cellRect.y() - aboveCell.bottom()) // 2)
qp.drawLine(cellRect.x(), y, cellRect.right() + 1, y)
if col:
leftCell = layout.cellRect(row, col - 1)
x = (leftCell.right()
+ (cellRect.x() - leftCell.right()) // 2)
qp.drawLine(x, cellRect.y(), x, cellRect.bottom() + 1)
class Window(LayoutLineWidget):
def __init__(self):
super().__init__()
self.setStyleSheet('''
Window {
background: #020a19;
}
QLabel#header {
qproperty-alignment: AlignCenter;
font-size: 25px;
color: #c5cad4;
padding-left: 10px;
padding-top: 16px;
padding-bottom: 20px;
}
QWidget#content {
border: 1px solid #c5cad4;
border-radius: 5px;
}
QPushButton {
background: #020a19;
color: #c5cad4;
border: none;
border-radius: 5px;
font-size: 15px;
font-weight: bold;
padding: 10px;
}
QPushButton:hover {
background-color: #00384d;
}
QWidget#menu > QPushButton {
width: 180px;
height: 80px;
}
''')
mainLayout = QGridLayout(self)
mainLayout.setContentsMargins(0, 0, 0, 0)
mainLayout.setSpacing(0)
self.header_label = QLabel('Some Application', objectName='header')
self.header_label.setMinimumHeight(120)
mainLayout.addWidget(self.header_label, 0, 0, 1, 2)
menuContainer = QWidget(objectName='menu')
mainLayout.addWidget(menuContainer)
menuLayout = QVBoxLayout(menuContainer)
menuLayout.setSpacing(15)
self.option_1_button = QPushButton('Option 1')
self.option_2_button = QPushButton('Option 2')
self.option_3_button = QPushButton('Option 3')
menuLayout.addWidget(self.option_1_button)
menuLayout.addWidget(self.option_2_button)
menuLayout.addWidget(self.option_3_button)
menuLayout.addStretch()
rightLayout = QVBoxLayout()
mainLayout.addLayout(rightLayout, 1, 1)
self.content = QStackedWidget()
self.content.setContentsMargins(40, 40, 40, 40)
rightLayout.addWidget(self.content)
rightLayout.addStretch(1)
self.firstPage = LayoutLineWidget(objectName='content')
self.content.addWidget(self.firstPage)
firstPageLayout = QVBoxLayout(self.firstPage)
spacing = sum(firstPageLayout.getContentsMargins()) // 2
firstPageLayout.setSpacing(spacing)
self.other_option_1_button = QPushButton('Other 1')
self.other_option_2_button = QPushButton('Other 2')
self.other_option_3_button = QPushButton('Other 3')
firstPageLayout.addWidget(self.other_option_1_button)
firstPageLayout.addWidget(self.other_option_2_button)
firstPageLayout.addWidget(self.other_option_3_button)
screen = QApplication.primaryScreen()
rect = QRect(QPoint(), screen.size() * .8)
rect.moveCenter(screen.geometry().center())
self.setGeometry(rect)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
这是视觉结果:
请注意,上面的代码会导致调用 paintEvent()
及其指令非常频繁,因此提供一些缓存始终是个好主意。一种可能是使用 QPicture,这是一种“QPainter 记录器”;由于它完全依赖于 C++ 实现,这允许通过绘制现有内容直到它被更改来优化绘画。
class LayoutLineWidget(QWidget):
_borderColor = QColor('#c5cad4')
_paintCache = None
_redrawEvents = QEvent.LayoutRequest, QEvent.Resize
def event(self, event):
if event.type() in self._redrawEvents:
self._paintCache = None
self.update()
return super().event(event)
def paintEvent(self, event):
# QWidget subclasses *must* do the following to properly use style sheets;
# see https://doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget
qp = QStylePainter(self)
opt = QStyleOption()
opt.initFrom(self)
qp.drawPrimitive(QStyle.PE_Widget, opt)
layout = self.layout()
if not layout or layout.count() <= 1:
return
if layout.spacing() < 1:
layout.setSpacing(1)
return
try:
qp.drawPicture(0, 0, self._paintCache)
except TypeError:
self._rebuildPaintCache()
qp.drawPicture(0, 0, self._paintCache)
def _rebuildPaintCache(self):
layout = self.layout()
self._paintCache = QPicture()
qp = QPainter(self._paintCache)
# from this point, it's exactly the same as above
qp.setPen(self._borderColor)
if isinstance(layout, QBoxLayout):
# ...
进一步说明:
- 以上代码尚未针对具有不同 margin/padding 设置和复杂 row/column 网格布局跨度的小部件进行测试;它可能需要进一步修复;
- QGridLayout 的一个隐藏功能是可以为每个网格“单元格”设置更多的小部件;虽然此功能对于复杂的布局很有用,但它有一个重要的缺点:每当 child 小部件或布局使用跨度时,布局间距将被忽略,因此 child 项目可能具有不一致的几何形状和上面的代码可能无法按预期工作;
- 不要低估与盒子模型相关的方面,尤其是在处理尺寸时;
- 字体大小应该不以像素为单位设置,因为屏幕可以有不同的 DPI 设置;总是喜欢基于设备的单位:
pt
、em
或 ex
;
我一直在努力学习 PyQt5 来重新实现我用 Tkinter 做的应用程序,但有一些设计差异。由于它不是一个复杂的应用程序,我想让它具有类似于 GitHub 桌面上的这个小 window 的样式(window 左侧的选项,其余的在剩下的 space):
我知道我的颜色现在看起来不太好,但我可以稍后处理。但是,我还没有找到如何绘制与这些相似的 lines/boxes,或者至少在我的 columns/rows.
之间的划分中这是我目前的情况:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QWidget, QFileDialog, QGridLayout, QFrame
from PyQt5 import QtGui, QtCore
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Some Window Title')
self.app = QApplication(sys.argv)
self.screen = self.app.primaryScreen()
self.screen_size = self.screen.size()
self.screen_width = self.screen_size.width()
self.screen_height = self.screen_size.height()
self.setGeometry(
self.screen_width * 0.1,
self.screen_height * 0.1,
self.screen_width * 0.8,
self.screen_height * 0.8
)
self.setStyleSheet('background: #020a19;')
self.grid = QGridLayout()
self.grid.setVerticalSpacing(0)
self.grid.setContentsMargins(0, 0, 0, 0)
self.option_button_stylesheet = '''
QPushButton {
background: #020a19;
color: #c5cad4;
border-color: #c5cad4;
border: 0px 0px 0px 0px;
border-style: outset;
border-radius: 5px;
font-size: 15px;
font-weight: bold;
padding: 10px;
margin: 15px;
width: 2px;
}
QPushButton:hover {
background-color: #00384d;
}
'''
self.placeholder_button_stylesheet = '''
QPushButton {
background: #020a19;
color: #c5cad4;
border-top: none;
border-right: 1px;
border-left:none;
border-bottom: none;
border-color: #c5cad4;
border-style: outset;
padding: 10px;
margin: 0px;
width: 2px;
height: 100%;
}
QPushButton:hover {
background-color: #020a19;
}
'''
self.header_label = QLabel('Some Application')
self.option_1_button = QPushButton('Option 1')
self.option_2_button = QPushButton('Option 2')
self.option_3_button = QPushButton('Option 3')
self.header_label.setStyleSheet(
'''
font-size: 25px;
color: #c5cad4;
padding-left: 10px;
padding-top: 16px;
padding-bottom: 20px;
height: 10%;
border-bottom: 1px solid #c5cad4;
'''
)
self.header_label.setFixedHeight(120)
self.grid.addWidget(self.header_label, 0, 0, 1, 2)
self.option_1_button.setStyleSheet(self.option_button_stylesheet)
self.option_1_button.setFixedWidth(200)
self.option_1_button.setFixedHeight(100)
self.grid.addWidget(self.option_1_button, 1, 0)
self.option_2_button.setStyleSheet(self.option_button_stylesheet)
self.option_2_button.setFixedWidth(200)
self.option_2_button.setFixedHeight(100)
self.grid.addWidget(self.option_2_button, 2, 0)
self.option_3_button.setStyleSheet(self.option_button_stylesheet)
self.option_3_button.setFixedWidth(200)
self.option_3_button.setFixedHeight(100)
self.grid.addWidget(self.option_3_button, 3, 0)
self.grid.setRowStretch(4, 1)
self.initUI()
self.setLayout(self.grid)
self.show()
def initUI(self):
self.greet_text = QLabel('Welcome')
self.greet_text.setAlignment(QtCore.Qt.AlignCenter)
self.greet_text.setStyleSheet(
"""
font-size: 35px;
color: #c5cad4;
"""
)
self.grid.addWidget(self.greet_text, 1, 1, 5, 1)
def run():
window = Window()
sys.exit(window.app.exec())
run()
如您所见,我使用 QWidget
作为我的 window 元素。我知道我可以使用 QMainWindow
,但这改变了小部件的放置方式,我发现它更易于使用 QWidget
。我也不需要这个应用程序的工具栏或类似的东西。
我怎样才能画出这些线?
Qt style sheets (QSS) 不提供这样的功能,因为它只能 style 特定的小部件,而不能考虑它们在其中的位置布局。这对您的情况很重要,因为您要做的是在布局项之间绘制“分隔线”。
理论上可以通过为容器小部件设置背景来实现这一点,该背景将成为线条颜色,让所有 child 小部件绘制其 完整 内容使用 不透明 颜色,并确保布局始终具有等于线条宽度的间距,但如果内部小部件不符合其完整尺寸,则它们使用 alpha 通道,或者添加一些拉伸或进一步的间距,结果会很难看。
一种可能性是使用 QWidget 子类,覆盖其 paintEvent()
并使用 QPainter 绘制这些线条。
我们的想法是循环遍历所有布局项目,并在“当前”项目和前一个项目之间画线。
在下面的示例中,我创建了一个实现上述概念的基本 QWidget 子类,具体取决于所使用的布局。
请注意,我必须对您的原始代码进行一些更改和更正:
- 正如评论中已经指出的那样,现有的 QApplication 是强制性的以允许创建 QWidget,虽然可以使它成为 object 的属性(before 调用
super().__init__()
), 概念上还是错误的; - 网格布局中的高度层次结构不应将单独的行和列用于其直接 child object,但应添加适当的 sub-layouts 或 child 小部件反而;在您的情况下,应该只有两行和两列:header 第一行有一个 2-column-span,菜单将位于第二行(索引 1)和第一列,第二列的右侧,菜单按钮将有自己的布局;
- 非常不鼓励为 parent 小部件设置通用样式 sheet 属性,因为复杂的小部件(例如 QComboBox、QScrollBar 和滚动区域 children)需要 all 属性设置为正常工作;对于 parent 或应用程序,应始终避免使用
setStyleSheet('background: ...')
; - 样式 sheet 应在 parent 或应用程序上设置在许多小部件之间共享,并且应始终使用适当的 selectors;
- QSS
width
属性 应谨慎使用,因为它可能会使小部件部分不可见且无法使用; - 如果您不想要任何边框,只需使用
border: none;
; - 样式 sheet 尺寸仅支持绝对单位(参见
Length
属性 类型),忽略百分比值; - 设置固定的高度、边距和边距可能会导致意外行为;确保您仔细阅读 box model 并进行一些测试以了解其行为;
- 类 不应在构造期间自动显示自己,因此
show()
不应在__init__()
内调用(这不是特别“禁止”或不鼓励,但它仍然很好实践); - 应始终使用
if __name__ == '__main__':
块,尤其是在处理依赖于事件循环的程序或工具包时(像所有 UI 框架,如 Qt);
这里是你的原始代码的重写:
class LayoutLineWidget(QWidget):
_borderColor = QColor('#c5cad4')
def paintEvent(self, event):
# QWidget subclasses *must* do this to properly use style sheets;
# (see doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget)
opt = QStyleOption()
opt.initFrom(self)
qp = QStylePainter(self)
qp.drawPrimitive(QStyle.PE_Widget, opt)
# end of default painting
layout = self.layout()
if not layout or layout.count() <= 1:
return
if layout.spacing() < 1:
layout.setSpacing(1)
return
qp.setPen(self._borderColor)
if isinstance(layout, QBoxLayout):
lastGeo = layout.itemAt(0).geometry()
if isinstance(layout, QVBoxLayout):
for row in range(1, layout.count()):
newGeo = layout.itemAt(row).geometry()
y = (lastGeo.bottom()
+ (newGeo.y() - lastGeo.bottom()) // 2)
qp.drawLine(0, y, self.width(), y)
lastGeo = newGeo
else:
for col in range(1, layout.count()):
newGeo = layout.itemAt(row).geometry()
x = (lastGeo.right()
+ (newGeo.x() - lastGeo.right()) // 2)
qp.drawLine(x, 0, x, self.height())
lastGeo = newGeo
elif isinstance(layout, QGridLayout):
for i in range(layout.count()):
row, col, rowSpan, colSpan = layout.getItemPosition(i)
if not row and not col:
continue
cellRect = layout.cellRect(row, col)
if rowSpan:
cellRect |= layout.cellRect(row + rowSpan - 1, col)
if colSpan:
cellRect |= layout.cellRect(row, col + colSpan - 1)
if row:
aboveCell = layout.cellRect(row - 1, col)
y = (aboveCell.bottom()
+ (cellRect.y() - aboveCell.bottom()) // 2)
qp.drawLine(cellRect.x(), y, cellRect.right() + 1, y)
if col:
leftCell = layout.cellRect(row, col - 1)
x = (leftCell.right()
+ (cellRect.x() - leftCell.right()) // 2)
qp.drawLine(x, cellRect.y(), x, cellRect.bottom() + 1)
class Window(LayoutLineWidget):
def __init__(self):
super().__init__()
self.setStyleSheet('''
Window {
background: #020a19;
}
QLabel#header {
qproperty-alignment: AlignCenter;
font-size: 25px;
color: #c5cad4;
padding-left: 10px;
padding-top: 16px;
padding-bottom: 20px;
}
QWidget#content {
border: 1px solid #c5cad4;
border-radius: 5px;
}
QPushButton {
background: #020a19;
color: #c5cad4;
border: none;
border-radius: 5px;
font-size: 15px;
font-weight: bold;
padding: 10px;
}
QPushButton:hover {
background-color: #00384d;
}
QWidget#menu > QPushButton {
width: 180px;
height: 80px;
}
''')
mainLayout = QGridLayout(self)
mainLayout.setContentsMargins(0, 0, 0, 0)
mainLayout.setSpacing(0)
self.header_label = QLabel('Some Application', objectName='header')
self.header_label.setMinimumHeight(120)
mainLayout.addWidget(self.header_label, 0, 0, 1, 2)
menuContainer = QWidget(objectName='menu')
mainLayout.addWidget(menuContainer)
menuLayout = QVBoxLayout(menuContainer)
menuLayout.setSpacing(15)
self.option_1_button = QPushButton('Option 1')
self.option_2_button = QPushButton('Option 2')
self.option_3_button = QPushButton('Option 3')
menuLayout.addWidget(self.option_1_button)
menuLayout.addWidget(self.option_2_button)
menuLayout.addWidget(self.option_3_button)
menuLayout.addStretch()
rightLayout = QVBoxLayout()
mainLayout.addLayout(rightLayout, 1, 1)
self.content = QStackedWidget()
self.content.setContentsMargins(40, 40, 40, 40)
rightLayout.addWidget(self.content)
rightLayout.addStretch(1)
self.firstPage = LayoutLineWidget(objectName='content')
self.content.addWidget(self.firstPage)
firstPageLayout = QVBoxLayout(self.firstPage)
spacing = sum(firstPageLayout.getContentsMargins()) // 2
firstPageLayout.setSpacing(spacing)
self.other_option_1_button = QPushButton('Other 1')
self.other_option_2_button = QPushButton('Other 2')
self.other_option_3_button = QPushButton('Other 3')
firstPageLayout.addWidget(self.other_option_1_button)
firstPageLayout.addWidget(self.other_option_2_button)
firstPageLayout.addWidget(self.other_option_3_button)
screen = QApplication.primaryScreen()
rect = QRect(QPoint(), screen.size() * .8)
rect.moveCenter(screen.geometry().center())
self.setGeometry(rect)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
这是视觉结果:
请注意,上面的代码会导致调用 paintEvent()
及其指令非常频繁,因此提供一些缓存始终是个好主意。一种可能是使用 QPicture,这是一种“QPainter 记录器”;由于它完全依赖于 C++ 实现,这允许通过绘制现有内容直到它被更改来优化绘画。
class LayoutLineWidget(QWidget):
_borderColor = QColor('#c5cad4')
_paintCache = None
_redrawEvents = QEvent.LayoutRequest, QEvent.Resize
def event(self, event):
if event.type() in self._redrawEvents:
self._paintCache = None
self.update()
return super().event(event)
def paintEvent(self, event):
# QWidget subclasses *must* do the following to properly use style sheets;
# see https://doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget
qp = QStylePainter(self)
opt = QStyleOption()
opt.initFrom(self)
qp.drawPrimitive(QStyle.PE_Widget, opt)
layout = self.layout()
if not layout or layout.count() <= 1:
return
if layout.spacing() < 1:
layout.setSpacing(1)
return
try:
qp.drawPicture(0, 0, self._paintCache)
except TypeError:
self._rebuildPaintCache()
qp.drawPicture(0, 0, self._paintCache)
def _rebuildPaintCache(self):
layout = self.layout()
self._paintCache = QPicture()
qp = QPainter(self._paintCache)
# from this point, it's exactly the same as above
qp.setPen(self._borderColor)
if isinstance(layout, QBoxLayout):
# ...
进一步说明:
- 以上代码尚未针对具有不同 margin/padding 设置和复杂 row/column 网格布局跨度的小部件进行测试;它可能需要进一步修复;
- QGridLayout 的一个隐藏功能是可以为每个网格“单元格”设置更多的小部件;虽然此功能对于复杂的布局很有用,但它有一个重要的缺点:每当 child 小部件或布局使用跨度时,布局间距将被忽略,因此 child 项目可能具有不一致的几何形状和上面的代码可能无法按预期工作;
- 不要低估与盒子模型相关的方面,尤其是在处理尺寸时;
- 字体大小应该不以像素为单位设置,因为屏幕可以有不同的 DPI 设置;总是喜欢基于设备的单位:
pt
、em
或ex
;