class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def setContentLayout(self, layout): lay = self.content_area.layout() del lay self.content_area.setLayout(layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class Spoiler(QWidget): def __init__(self, title="", animationDuration=300, parent=None): QWidget.__init__(self, parent) self.mainLayout = QGridLayout() self.toggleButton = QToolButton() self.toggleAnimation = QParallelAnimationGroup() self.contentArea = QScrollArea() self.headerLine = QFrame() self.animationDuration = animationDuration self.toggleButton.setStyleSheet("QToolButton { border: none; }") self.toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleButton.setText(title) self.toggleButton.setCheckable(True) self.toggleButton.setChecked(False) self.headerLine.setFrameShape(QFrame.HLine) self.headerLine.setFrameShadow(QFrame.Sunken) self.headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.contentArea.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.contentArea.setMaximumHeight(0) self.contentArea.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.toggleAnimation.addAnimation(QPropertyAnimation()) self.toggleAnimation.addAnimation(QPropertyAnimation(self)) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self.contentArea, b"maximumHeight")) # don't waste space self.mainLayout.setVerticalSpacing(0) self.mainLayout.setContentsMargins(0, 0, 0, 0) row = 0 self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1, Qt.AlignLeft) self.mainLayout.addWidget(self.headerLine, 0, 2, 1, 1) self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 3) self.setLayout(self.mainLayout) self.toggleButton.clicked[bool].connect(self.expandShrinkContent) def expandShrinkContent(self, checked): if checked: self.toggleButton.setArrowType(Qt.ArrowType.DownArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Forward) else: self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Backward) self.toggleAnimation.start() def setContentLayout(self, contentLayout): self.contentArea.setLayout(contentLayout) collapsedHeight = self.sizeHint().height( ) - self.contentArea.maximumHeight() contentHeight = contentLayout.sizeHint().height() i = 0 while i < self.toggleAnimation.animationCount(): i += 1 spoilerAnimation = self.toggleAnimation.animationAt(i) if spoilerAnimation is not None: spoilerAnimation.setDuration(self.animationDuration) spoilerAnimation.setStartValue(collapsedHeight) spoilerAnimation.setEndValue(collapsedHeight + contentHeight) contentAnimation = self.toggleAnimation.animationAt( self.toggleAnimation.animationCount() - 1) contentAnimation.setDuration(self.animationDuration) contentAnimation.setStartValue(0) contentAnimation.setEndValue(contentHeight)
class Spoiler(QWidget): class Orientation(Enum): HORIZONTAL = 1 VERTICAL = 2 def __init__(self, orientation=Orientation.HORIZONTAL, animationDuration=120, parent=None): QWidget.__init__(self, parent) self.opened = False self.orientation = orientation self.animationDuration = animationDuration self.mainLayout = None self.animator = QParallelAnimationGroup() if orientation is self.Orientation.HORIZONTAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumWidth(0) self.setMinimumWidth(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumWidth")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumWidth")) elif orientation is self.Orientation.VERTICAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumHeight(0) self.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumHeight")) def open(self): self.animator.setDirection(QAbstractAnimation.Forward) self.animator.start() self.opened = True def close(self): self.animator.setDirection(QAbstractAnimation.Backward) self.animator.start() self.opened = False def isOpened(self): return self.opened def setContentLayout(self, contentLayout): self.setLayout(contentLayout) if self.orientation is self.Orientation.HORIZONTAL: collapsedSize = self.maximumWidth() contentSize = contentLayout.sizeHint().width() elif self.orientation is self.Orientation.VERTICAL: collapsedSize = self.maximumHeight() contentSize = contentLayout.sizeHint().height() i = 0 while i < self.animator.animationCount(): animation = self.animator.animationAt(i) animation.setDuration(self.animationDuration) animation.setStartValue(collapsedSize) animation.setEndValue(collapsedSize + contentSize) i += 1
class CollapsibleWidget(QWidget): def __init__(self, title="", parent=None, animation_duration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(CollapsibleWidget, self).__init__(parent) self.title = title self.toggle_button = QToolButton() self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea() self.animation_duration = animation_duration self._init_base_ui() def _init_base_ui(self): self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_button.setText(str(self.title)) self.toggle_button.setCheckable(True) self.toggle_button.setChecked(False) self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) layout = QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.toggle_button) layout.addWidget(self.content_area) def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def set_content_layout(self, layout): initial_layout = self.content_area.layout() del initial_layout self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(self.animation_duration) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height) def set_content_widget(self, widget): initial_layout = self.content_area.layout() del initial_layout self.content_area.setWidget(widget) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = widget.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(self.animation_duration) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class GroupWidget(QWidget): def __init__(self, parent=None, title='', animation_duration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(GroupWidget, self).__init__(parent=parent) self.animation_duration = animation_duration self.toggle_animation = QParallelAnimationGroup() self.content_area = QScrollArea() self.header_line = QFrame() self.toggle_button = QToolButton() self.main_layout = QGridLayout() toggle_button = self.toggle_button toggle_button.setStyleSheet("QToolButton { border: none; }") toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toggle_button.setArrowType(Qt.RightArrow) toggle_button.setText(str(title)) toggle_button.setCheckable(True) toggle_button.setChecked(False) header_line = self.header_line header_line.setFrameShape(QFrame.HLine) header_line.setFrameShadow(QFrame.Sunken) header_line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.content_area.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # start out collapsed self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) # let the entire widget grow and shrink with its content toggle_animation = self.toggle_animation toggle_animation.addAnimation( QPropertyAnimation(self, bytes("minimumHeight", "utf-8"))) toggle_animation.addAnimation( QPropertyAnimation(self, bytes("maximumHeight", "utf-8"))) toggle_animation.addAnimation( QPropertyAnimation(self.content_area, bytes("maximumHeight", "utf-8"))) # don't waste space main_layout = self.main_layout main_layout.setVerticalSpacing(0) main_layout.setContentsMargins(0, 0, 0, 0) row = 0 main_layout.addWidget(self.toggle_button, row, 0, 1, 1, Qt.AlignLeft) main_layout.addWidget(self.header_line, row, 2, 1, 1) row += 1 main_layout.addWidget(self.content_area, row, 0, 1, 3) self.setLayout(self.main_layout) def start_animation(checked): arrow_type = Qt.DownArrow if checked else Qt.RightArrow direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward toggle_button.setArrowType(arrow_type) self.toggle_animation.setDirection(direction) self.toggle_animation.start() self.toggle_button.clicked.connect(start_animation) def set_content_layout(self, content_layout): # Not sure if this is equivalent to self.contentArea.destroy() self.content_area.destroy() self.content_area.setLayout(content_layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = content_layout.sizeHint().height() for i in range(self.toggle_animation.animationCount() - 1): spoiler_animation = self.toggle_animation.animationAt(i) spoiler_animation.setDuration(self.animation_duration) spoiler_animation.setStartValue(collapsed_height) spoiler_animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton {border: none;\ border: 1px solid #FF17365D;\ border-top-left-radius: 15px;\ border-top-right-radius: 15px;\ background-color: #FF17365D;\ padding: 5px 0px;\ color: rgb(255, 255, 255);\ max-height: 30px;\ font-size: 14px;\ }\ QToolButton:hover {\ background-color: lightgreen;\ color: black;\ }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @QtCore.pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def clear_layout(self, layout): try: for i in reversed(range(layout.count())): widgetToRemove = layout.itemAt(i).widget() layout.removeWidget(widgetToRemove) widgetToRemove.setPArent(None) except AttributeError: pass def setContentLayout(self, layout): lay = self.content_area.layout() self.clear_layout(lay) self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class CollapsibleMessageBox(QWidget): ''' docstring: 消息显示类,按钮触发折叠 ''' def __init__(self, Title="", parent=None, defaultLayout=False, Message=None): super().__init__(parent) self.toggle_button = QToolButton(text=Title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) if defaultLayout: lay = QVBoxLayout() self.text = QLabel() pa = QPalette() pa.setColor(pa.Background, Qt.white) pa.setColor(pa.Foreground, Qt.black) self.text.setAutoFillBackground(True) self.text.setPalette(pa) self.text.setTextInteractionFlags(Qt.TextSelectableByMouse) self.text.setTextFormat(Qt.MarkdownText) self.text.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) self.text.setWordWrap(True) if not Message: Message = '空' self.text.setText(Message.replace('\n', '\n\n') + '\n') lay.addWidget(self.text) self.setContentLayout(lay) @pyqtSlot() def on_pressed(self): ''' docstring: 按钮函数,设置动画参数并触发 ''' checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def setContentLayout(self, layout): ''' docstring: 重新设置布局,并计算按钮动画参数 ''' lay = self.content_area.layout() del lay self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)