Как сделать виджет расширяемого / сжимаемого раздела в QT

Я хотел бы создать собственный виджет в QT со следующими функциями:

  • Это контейнер
  • Он может быть заполнен любым QT-макетом
  • Он может находиться внутри любого макета QT
  • Кнопка позволяет свернуть / свернуть вертикально содержимое, так что только кнопка видна, все содержимое макета невидимо.
  • Предыдущая кнопка позволяет развернуть / развернуть ее снова до размера содержимого макета.
  • Расширение / сворачивание основано на размерах (не на show / hide), что позволяет анимацию.
  • Используется в QDesigner

Чтобы представить идею, вот образ аналогичного виджета (а не QT): введите описание изображения здесь

У меня уже есть фрейм, который работает правильно и отображается в QDesigner. Теперь мне нужно сделать это для расширения / сглаживания, что не кажется таким простым.

Я пытался играть с параметрами resize (), sizePolicy (), sizeHint (), но это не сработает: когда кадр свалился, я получил следующие значения:

sizeHint: (500,20) size : (500,20) closestAcceptableSize: (518,150) Painted size: (518, 150) 

QLayout :: closestAcceptableSize не является частью виджета, поэтому я не могу его изменить.

Любой намек или / или fragment кода для этого?

EDITED: Вот простой пример. Я удалил все, кроме необходимости.

пример main.cpp

 #include  #include  #include  #include "section.hpp" using namespace myWidgets; int main(int argc, char *argv[]) { QApplication a(argc, argv); // Create the main Window QWidget window; window.resize(500,500); window.setStyleSheet("QPushButton:{background-color:rgba(128,128,128,192);}"); // Create the main window layout QVBoxLayout topLayout(&window); QWidget *w1 = new QWidget(); w1->setStyleSheet("background-color:rgba(128,128,128,192);"); topLayout.addWidget(w1); Section section(&window); topLayout.addWidget(&section); QVBoxLayout inLayout(&section); QPushButton *button = new QPushButton(); button->setMinimumHeight(100); inLayout.addWidget(button); QWidget *w2 = new QWidget(); w2->setStyleSheet("background-color:rgba(128,128,128,192);"); topLayout.addWidget(w2); window.show(); return a.exec(); } 

Section.hpp

 #ifndef SECTION_HPP #define SECTION_HPP #include  //for the expand/collapse button #include  #include  #include  #include  #include  // Compatibility for noexcept, not supported in vsc++ #ifdef _MSC_VER #define noexcept throw() #endif #if defined SECTION_BUILD #define SECTION_BUILD_DLL_SPEC Q_DECL_EXPORT #elif defined SECTION_EXEC #define SECTION_BUILD_DLL_SPEC #else #define SECTION_BUILD_DLL_SPEC Q_DECL_IMPORT #endif namespace myWidgets { class SECTION_BUILD_DLL_SPEC Section : public QWidget { Q_OBJECT Q_PROPERTY( bool is_expanded MEMBER isExpanded) public: // Constructor, standard explicit Section( QWidget *parent=0 ): QWidget(parent), expandButton(this) { expandButton.resize(20,20); expandButton.move(0,0); expandButton.connect(&expandButton, &QPushButton::clicked, this, &Section::expandCollapseEvent); QMargins m= contentsMargins(); m.setTop(m.top()+25); setContentsMargins(m); //setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Minimum); } virtual void expand( bool expanding ) noexcept { resize(sizeHint()); isExpanded = expanding; updateGeometry(); qDebug() << (isExpanded? "expanded":"collapsed") << sizeHint() << QWidget::size() <layout()->closestAcceptableSize(this, size()); } virtual QSize sizeHint() const noexcept override { if (isExpanded) return QSize(layout()->contentsRect().width(), layout()->contentsRect().height()); else return QSize(layout()->contentsRect().width(), 20); } // Implement custom appearance virtual void paintEvent(QPaintEvent *e) noexcept override { (void) e; //TODO: remove QPainter p(this); p.setClipRect(e->rect()); p.setRenderHint(QPainter::Antialiasing ); p.fillRect(e->rect(), QColor(0,0,255,128)); } protected: // on click of the expandButton, collapse/expand this widget virtual void expandCollapseEvent() noexcept { expand(!isExpanded); } bool isExpanded = true; //whenever the section is collapsed(false) or expanded(true) QPushButton expandButton; //the expanding/collapsing button }; } #endif // SECTION_HPP 

    Я наткнулся на ту же проблему и решил ее, реализовав QScrollArea виджет как QScrollArea , максимальная высота которого анимируется QPropertyAnimation .

    Но поскольку я не использую QDesigner, я не могу сказать, работает ли он там.

    У меня все еще есть одна проблема: вместо того, чтобы только расширяться в направлении снизу, складной виджет может расширяться вверх и вниз. Это может привести к тому, что виджеты, расположенные над ним, уменьшатся, если они еще не достигли минимальной высоты. Но это действительно деталь по сравнению с тем фактом, что мы должны сами строить эту вещь …

    Spoiler.h

     #include  #include  #include  #include  #include  #include  class Spoiler : public QWidget { Q_OBJECT private: QGridLayout mainLayout; QToolButton toggleButton; QFrame headerLine; QParallelAnimationGroup toggleAnimation; QScrollArea contentArea; int animationDuration{300}; public: explicit Spoiler(const QString & title = "", const int animationDuration = 300, QWidget *parent = 0); void setContentLayout(QLayout & contentLayout); }; 

    Spoiler.cpp

     #include  #include "Spoiler.h" Spoiler::Spoiler(const QString & title, const int animationDuration, QWidget *parent) : QWidget(parent), animationDuration(animationDuration) { toggleButton.setStyleSheet("QToolButton { border: none; }"); toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toggleButton.setArrowType(Qt::ArrowType::RightArrow); toggleButton.setText(title); toggleButton.setCheckable(true); toggleButton.setChecked(false); headerLine.setFrameShape(QFrame::HLine); headerLine.setFrameShadow(QFrame::Sunken); headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }"); contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // start out collapsed contentArea.setMaximumHeight(0); contentArea.setMinimumHeight(0); // let the entire widget grow and shrink with its content toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight")); toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight")); toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight")); // don't waste space mainLayout.setVerticalSpacing(0); mainLayout.setContentsMargins(0, 0, 0, 0); int row = 0; mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft); mainLayout.addWidget(&headerLine, row++, 2, 1, 1); mainLayout.addWidget(&contentArea, row, 0, 1, 3); setLayout(&mainLayout); QObject::connect(&toggleButton, &QToolButton::clicked, [this](const bool checked) { toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); toggleAnimation.start(); }); } void Spoiler::setContentLayout(QLayout & contentLayout) { delete contentArea.layout(); contentArea.setLayout(&contentLayout); const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight(); auto contentHeight = contentLayout.sizeHint().height(); for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) { QPropertyAnimation * spoilerAnimation = static_cast(toggleAnimation.animationAt(i)); spoilerAnimation->setDuration(animationDuration); spoilerAnimation->setStartValue(collapsedHeight); spoilerAnimation->setEndValue(collapsedHeight + contentHeight); } QPropertyAnimation * contentAnimation = static_cast(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1)); contentAnimation->setDuration(animationDuration); contentAnimation->setStartValue(0); contentAnimation->setEndValue(contentHeight); } 

    Как это использовать:

     … auto * anyLayout = new QVBoxLayout(); anyLayout->addWidget(…); … Spoiler spoiler; spoiler.setContentLayout(*anyLayout); … 

    Пример спойлера

    Хотя это и старо, я нашел эту ветку полезной. Тем не менее, я работаю на python, поэтому мне пришлось преобразовать код C ++. На всякий случай, если кто-то ищет версию x квадрата для python. Вот мой порт:

     from PyQt4 import QtCore, QtGui class Spoiler(QtGui.QWidget): def __init__(self, parent=None, title='', animationDuration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(Spoiler, self).__init__(parent=parent) self.animationDuration = 300 self.toggleAnimation = QtCore.QParallelAnimationGroup() self.contentArea = QtGui.QScrollArea() self.headerLine = QtGui.QFrame() self.toggleButton = QtGui.QToolButton() self.mainLayout = QtGui.QGridLayout() toggleButton = self.toggleButton toggleButton.setStyleSheet("QToolButton { border: none; }") toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) toggleButton.setArrowType(QtCore.Qt.RightArrow) toggleButton.setText(str(title)) toggleButton.setCheckable(True) toggleButton.setChecked(False) headerLine = self.headerLine headerLine.setFrameShape(QtGui.QFrame.HLine) headerLine.setFrameShadow(QtGui.QFrame.Sunken) headerLine.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Maximum) self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }") self.contentArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) # start out collapsed self.contentArea.setMaximumHeight(0) self.contentArea.setMinimumHeight(0) # let the entire widget grow and shrink with its content toggleAnimation = self.toggleAnimation toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, "minimumHeight")) toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, "maximumHeight")) toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self.contentArea, "maximumHeight")) # don't waste space mainLayout = self.mainLayout mainLayout.setVerticalSpacing(0) mainLayout.setContentsMargins(0, 0, 0, 0) row = 0 mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, QtCore.Qt.AlignLeft) mainLayout.addWidget(self.headerLine, row, 2, 1, 1) row += 1 mainLayout.addWidget(self.contentArea, row, 0, 1, 3) self.setLayout(self.mainLayout) def start_animation(checked): arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow direction = QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward toggleButton.setArrowType(arrow_type) self.toggleAnimation.setDirection(direction) self.toggleAnimation.start() self.toggleButton.clicked.connect(start_animation) def setContentLayout(self, contentLayout): # Not sure if this is equivalent to self.contentArea.destroy() self.contentArea.destroy() self.contentArea.setLayout(contentLayout) collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight() contentHeight = contentLayout.sizeHint().height() for i in range(self.toggleAnimation.animationCount()-1): spoilerAnimation = self.toggleAnimation.animationAt(i) 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) 

    Я знаю, что это не очень хороший способ ответить на вопрос, просто со ссылкой, но я думаю, что этот пост в блоге вполне уместен:

    http://www.fancyaddress.com/blog/qt-2/create-something-like-the-widget-box-as-in-the-qt-designer/

    Он основан на QTreeWidget и использует функции расширения / сглаживания, которые уже реализованы. В нем объясняется, как виджеты могут быть добавлены к элементам виджета дерева, и как добавить кнопку для использования для их свертывания / расширения.

    Разумеется, все благодарности за пост автора.

    Я прорыл отличный указатель, предоставленный @LoPiTal, и преобразовал его в PyQt5 (Python3). Я думаю, что это очень элегантно.

    Если кто-то ищет PyQt-решение, вот мой код:

     import sys from PyQt5.QtWidgets import (QPushButton, QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QApplication) class SectionExpandButton(QPushButton): """a QPushbutton that can expand or collapse its section """ def __init__(self, item, text = "", parent = None): super().__init__(text, parent) self.section = item self.clicked.connect(self.on_clicked) def on_clicked(self): """toggle expand/collapse of section by clicking """ if self.section.isExpanded(): self.section.setExpanded(False) else: self.section.setExpanded(True) class CollapsibleDialog(QDialog): """a dialog to which collapsible sections can be added; subclass and reimplement define_sections() to define sections and add them as (title, widget) tuples to self.sections """ def __init__(self): super().__init__() self.tree = QTreeWidget() self.tree.setHeaderHidden(True) layout = QVBoxLayout() layout.addWidget(self.tree) self.setLayout(layout) self.tree.setIndentation(0) self.sections = [] self.define_sections() self.add_sections() def add_sections(self): """adds a collapsible sections for every (title, widget) tuple in self.sections """ for (title, widget) in self.sections: button1 = self.add_button(title) section1 = self.add_widget(button1, widget) button1.addChild(section1) def define_sections(self): """reimplement this to define all your sections and add them as (title, widget) tuples to self.sections """ widget = QFrame(self.tree) layout = QHBoxLayout(widget) layout.addWidget(QLabel("Bla")) layout.addWidget(QLabel("Blubb")) title = "Section 1" self.sections.append((title, widget)) def add_button(self, title): """creates a QTreeWidgetItem containing a button to expand or collapse its section """ item = QTreeWidgetItem() self.tree.addTopLevelItem(item) self.tree.setItemWidget(item, 0, SectionExpandButton(item, text = title)) return item def add_widget(self, button, widget): """creates a QWidgetItem containing the widget, as child of the button-QWidgetItem """ section = QTreeWidgetItem(button) section.setDisabled(True) self.tree.setItemWidget(section, 0, widget) return section if __name__ == "__main__": app = QApplication(sys.argv) window = CollapsibleDialog() window.show() sys.exit(app.exec_()) 

    Решение, которое я применил, заключается в использовании свойства MaximumSize виджета, чтобы ограничить высоту, когда она сгибается.

    Самая большая проблема заключается в том, чтобы знать развернутую высоту, когда она сгибается, чтобы обеспечить правильный шаг анимации. Это не было решено, и в настоящее время я делаю анимацию с шагом высоты исправления (который я установил для соответствующего значения относительно ожидаемой высоты windows).

     if (toBeFolded) { unfoldedMaxHeight = maximumHeight(); previousUnfoldedHeight = height(); setMaximumHeight(25); } else { // animate maximumHeight from 25 up to where the height do not change // A hint of the final maximumHeight is the previousUnfoldedHeight. // After animation, set maximumHeight back to unfoldedMaxHeight. } 
    Interesting Posts

    Объясните, почему инжектор конструктора лучше, чем другие параметры

    Получить значение int из enum в C #

    Как вы используете предложение «WITH» в MySQL?

    Наушники usb только воспроизводят определенный звук

    UIWebView: когда страница действительно заканчивала загрузку?

    Чтение содержимого из файлов, находящихся внутри Zip-файла

    Regex загладить первую букву каждое слово, также после специального символа, как тире

    Файл Java – открыть файл и записать его

    отправка нескольких форм с помощью AJAX

    Ubuntu на XPS 14 Ultrabook с кешем mSATA и 500 ГБ HD – как разбить для двойной загрузки?

    перебирать все текстовые поля в форме, в том числе внутри группового windows

    Windows XP PRO SP3 – остановить изменения ключа ключа безопасности, установленного неизвестной программой

    Является ли переход от Windows 8.1 Pro к Windows 7 Professional обратимым?

    Метод getContactsFromFirebase () возвращает пустой список

    Как Autowire Bean из родового типа весной?

    Давайте будем гением компьютера.