如何在Qt中制作一个可扩展/可折叠的小部件 [英] How to make an expandable/collapsable section widget in Qt

查看:253
本文介绍了如何在Qt中制作一个可扩展/可折叠的小部件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在Qt中创建具有以下功能的自定义小部件:

I would like to create a custom widget in Qt with the following features:


  • 这是一个容器

  • 它可以用任何Qt布局填充

  • 它可以在任何Qt布局内部

  • 一个按钮允许垂直折叠/折叠内容,因此只有按钮可见,所有包含的布局都不可见。

  • 上一个按钮允许再次将其扩展/展开为布局内容的大小。

  • 展开/折叠基于允许动画的大小(不在显示/隐藏上)。

  • 在QDesigner中可用

  • It is a container
  • It may be populated with any Qt layout
  • It may be inside any Qt layout
  • A button allows to collapse/fold vertically the content, so only the button is visible, all the contained layout is invisible.
  • The previous button allows to expand/unfold it again to the size of the layout content.
  • The expanding/collapsing is based on sizes (not on show/hide) to allows animation.
  • Usable in QDesigner

为了提供一个想法,以下是类似的小部件(不是Qt)的图像:

To provide an idea, here is an image of a similar widget (not Qt):

我已经有一个框架可以正常工作,并且在QDesigner中公开。我现在需要使其扩展/折叠,这似乎并不简单。

I already have a frame that work correctly and is exposed in QDesigner. I need now to make it to extend/collapse, which does not seem so simple.

我尝试使用resize(),sizePolicy(),sizeHint(),但那不起作用:
框架折叠时,我得到以下值:

I tried to play with resize(), sizePolicy(), sizeHint() but that does not work: When the frame is collapsed I got following values:

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

QLayout :: closestAcceptableSize不是窗口小部件的一部分,因此我无法更改它。

QLayout::closestAcceptableSize is not part of the widget so I cannot change it.

任何提示或/和

已编辑:
这是一个简单的示例。我删除了所有必要的内容。

EDITED: Here a simple example. I removed all except necessary.

main.cpp示例

main.cpp example

#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>

#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

Section.hpp

#ifndef SECTION_HPP
#define SECTION_HPP

#include <QPushButton> //for the expand/collapse button
#include <QtDesigner/QDesignerExportWidget>
#include <QLayout>
#include <QPainter>
#include <QPaintEvent>
#include <QDebug>


// 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() <<
            parentWidget()->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 来实现,该控件的最大高度已设置为动画通过 QPropertyAnimation

I stumbled upon the same problem and solved it by implementing the collapsible widget as a QScrollArea whose maximum height is animated by a QPropertyAnimation.

但是由于我不使用QDesigner,所以无法告诉您它是否有效

But since I don't use QDesigner, I can't tell you if it works there.

我还有一个问题:可折叠的小部件不仅可以向底部扩展,还可以向顶部和底部扩展。如果尚未达到其最小高度,这可能会导致位于其上方的小部件缩小。但是,与我们必须自己构建此东西的事实相比,这确实是一个细节。

I still have one problem: Instead of only expanding towards the bottom direction, the collapsible widget can expand towards the top and bottom. This can cause widgets located above it to shrink if they haven't reached their minimum height, yet. But this is really a detail compared to the fact that we have to build this thing ourselves…

Spoiler.h

#include <QFrame>
#include <QGridLayout>
#include <QParallelAnimationGroup>
#include <QScrollArea>
#include <QToolButton>
#include <QWidget>

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 <QPropertyAnimation>

#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<QPropertyAnimation *>(toggleAnimation.animationAt(i));
        spoilerAnimation->setDuration(animationDuration);
        spoilerAnimation->setStartValue(collapsedHeight);
        spoilerAnimation->setEndValue(collapsedHeight + contentHeight);
    }
    QPropertyAnimation * contentAnimation = static_cast<QPropertyAnimation *>(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);
…

这篇关于如何在Qt中制作一个可扩展/可折叠的小部件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆