PyQt5制作原生可折叠侧边栏:主题适配与选中边框问题问询
适配主题的PyQt5可折叠侧边栏问题及优化方案
问题描述
我正在用PyQt5制作原生风格的可展开折叠侧边栏,当前实现在WhiteSur KDE主题下运行正常,但切换到Breeze主题时遇到两个问题:
- 设置侧边栏最小宽度时,直接用
self.iconSize().width()会截断图标,在WhiteSur主题里加16能解决,但Breeze主题下这个值过宽,需要改成10左右,无法适配不同主题的内边距。 - Breeze主题下侧边栏会显示蓝色选中边框,不符合需求。
现有实现代码
import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtCore import pyqtProperty from PyQt5.QtGui import * class Sidebar(QListWidget): def __init__(self, buttons: list = None): super().__init__() font = QFont() font.setPointSize(12) self.setFont(font) self.setIconSize(QSize(35, 35)) for button in buttons: self.addItem(button) self.maxWidth = self.sizeHintForColumn(0) + 10 iconWidth = self.iconSize().width() self.minWidth = iconWidth + 16 self.awidth = self.minWidth self.expandAnimation = QPropertyAnimation(self, b'awidth') self.expandAnimation.setEndValue(self.maxWidth) self.expandAnimation.setDuration(200) self.expandAnimation.setEasingCurve(QEasingCurve.InOutCubic) self.contractAnimation = QPropertyAnimation(self, b'awidth') self.contractAnimation.setEndValue(self.minWidth) self.contractAnimation.setDuration(200) self.contractAnimation.setEasingCurve(QEasingCurve.InOutCubic) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setTextElideMode(Qt.TextElideMode.ElideNone) def leaveEvent(self, e) -> None: self.contractAnimation.start() def enterEvent(self, e) -> None: self.expandAnimation.start() @pyqtProperty(int) def awidth(self) -> int: return self._awidth @awidth.setter def awidth(self, value) -> None: self._awidth = value super().setFixedWidth(self._awidth) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.buttonState = False self.setMinimumSize(500, 300) button1 = QListWidgetItem(QIcon.fromTheme('battery'), 'Button 1') button2 = QListWidgetItem(QIcon.fromTheme('computer'), 'Button 2') button3 = QListWidgetItem(QIcon.fromTheme('camera-web'), 'Button 3') self.sidebar = Sidebar([button1, button2, button3]) self.sidebar.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Expanding) self.mainWidget = QPushButton() self.mainWidget.setText('0') font = QFont() font.setPointSize(20) font.setBold(True) self.mainWidget.setFont(font) self.mainWidget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.mainWidget.pressed.connect(self.buttonPressed) layout = QHBoxLayout() layout.addWidget(self.sidebar) layout.addWidget(self.mainWidget) centralWidget = QWidget() centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.sidebar.itemSelectionChanged.connect(self.sidebarChanged) def sidebarChanged(self): selection = self.sidebar.selectedIndexes()[0].row() print(selection) self.mainWidget.setText(str(selection)) def buttonPressed(self): if self.buttonState: self.sidebar.contractAnimation.start() else: self.sidebar.expandAnimation.start() self.buttonState = not self.buttonState app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
解决方案
一、获取适配主题的最小宽度
不要硬编码内边距数值,通过QStyle自动获取当前主题的布局参数:
在Sidebar的__init__方法中,替换原来的minWidth计算代码:
# 获取当前样式的布局参数 style = self.style() # 获取列表项的左右内边距总和 itemPadding = style.pixelMetric(QStyle.PM_FocusFrameHMargin, None, self) * 2 # 最小宽度 = 图标宽度 + 左右内边距 self.minWidth = self.iconSize().width() + itemPadding
这种方式会自动适配不同主题的内边距设置,无需手动调整数值。
二、移除侧边栏选中边框
根据需求选择以下两种方法:
方法1:完全禁用选择功能
如果不需要选中逻辑,在Sidebar的__init__中添加:
self.setSelectionMode(QListWidget.NoSelection)
注意:这会导致itemSelectionChanged信号失效,可改用itemClicked信号替代点击事件。
方法2:保留点击交互但隐藏选中样式
如果需要保留点击功能但隐藏选中边框,添加样式表:
self.setStyleSheet(""" QListWidget::item:selected { background-color: transparent; border: none; } QListWidget::item:focus { outline: none; } """)
三、更优的原生风格侧边栏实现方案
方案1:使用QToolBar
QToolBar本身支持原生主题适配,自带可折叠、浮动特性,适合快速实现原生风格侧边栏:
class Sidebar(QToolBar): def __init__(self): super().__init__() self.setMovable(False) self.setIconSize(QSize(35, 35)) # 添加侧边项 self.addAction(QIcon.fromTheme('battery'), 'Button 1') self.addAction(QIcon.fromTheme('computer'), 'Button 2') self.addAction(QIcon.fromTheme('camera-web'), 'Button 3') # 计算适配主题的最小宽度 style = self.style() itemPadding = style.pixelMetric(QStyle.PM_FocusFrameHMargin, None, self) * 2 self.minWidth = self.iconSize().width() + itemPadding # 复用原有的展开折叠动画逻辑 self.maxWidth = self.sizeHint().width() + 10 self.setFixedWidth(self.minWidth) self.expandAnimation = QPropertyAnimation(self, b'fixedWidth') self.expandAnimation.setEndValue(self.maxWidth) self.expandAnimation.setDuration(200) self.expandAnimation.setEasingCurve(QEasingCurve.InOutCubic) self.contractAnimation = QPropertyAnimation(self, b'fixedWidth') self.contractAnimation.setEndValue(self.minWidth) self.contractAnimation.setDuration(200) self.contractAnimation.setEasingCurve(QEasingCurve.InOutCubic) def leaveEvent(self, e) -> None: self.contractAnimation.start() def enterEvent(self, e) -> None: self.expandAnimation.start()
QToolBar无需额外调整边框和内边距,完全适配系统主题。
方案2:自定义QWidget容器
如果需要高度定制,用QWidget作为容器,内部放置自定义按钮组件,通过布局管理:
class Sidebar(QWidget): def __init__(self): super().__init__() layout = QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(0,0,0,0) # 添加自定义按钮 btn1 = QPushButton(QIcon.fromTheme('battery'), 'Button 1') btn1.setIconSize(QSize(35,35)) btn1.setStyleSheet("text-align: left; padding: 8px;") layout.addWidget(btn1) btn2 = QPushButton(QIcon.fromTheme('computer'), 'Button 2') btn2.setIconSize(QSize(35,35)) btn2.setStyleSheet("text-align: left; padding: 8px;") layout.addWidget(btn2) # 复用动画逻辑 self.minWidth = btn1.iconSize().width() + 16 self.maxWidth = self.sizeHint().width() + 10 self.setFixedWidth(self.minWidth) self.expandAnimation = QPropertyAnimation(self, b'fixedWidth') self.expandAnimation.setEndValue(self.maxWidth) self.expandAnimation.setDuration(200) self.expandAnimation.setEasingCurve(QEasingCurve.InOutCubic) self.contractAnimation = QPropertyAnimation(self, b'fixedWidth') self.contractAnimation.setEndValue(self.minWidth) self.contractAnimation.setDuration(200) self.contractAnimation.setEasingCurve(QEasingCurve.InOutCubic) def leaveEvent(self, e) -> None: self.contractAnimation.start() def enterEvent(self, e) -> None: self.expandAnimation.start()
这种方式可以完全控制每个元素的样式和交互逻辑。
内容的提问来源于stack exchange,提问作者rbits
相关产品推荐
相关产品推荐

