【问题标题】:Making collapsible GroupBoxes in Qt - what determines the collapsed size?在 Qt 中制作可折叠的 GroupBox - 什么决定了折叠的大小?
【发布时间】:2016-08-31 04:21:47
【问题描述】:

我正在尝试制作带有折叠/展开按钮的 GroupBoxes,希望在 Qt 中具有明显的功能。我已经将 QGroupBox 子类化为绘制按钮。单击时,我的代码会在 GroupBox 的所有子项上调用 setVisible(false)。此外,它遍历 GroupBox 中的所有 QLayout 并将它们的 contentMargins 设置为零。但是,一些 GroupBox 在折叠状态下仍然比其他的大,我不知道是什么原因造成的。

这是我目前为止的想法(我知道可能存在nullptr 问题):

void CollapsibleGroupBox::onVisibilityChanged()
{
  CollapseExpandButton::State s;

  s = m_clExpButton->state();

  QLayout *master = this->layout();
  QList<QObject *> children = this->children();

  switch (s) {
  case CollapseExpandButton::State::COLLAPSED:
    for (QObject *o : children) {
      QWidget *w = qobject_cast<QWidget *>(o);
      if (w != nullptr) {
        if (w != m_clExpButton)
          w->setVisible(false);

        continue;
      }

      if (o == master) {
        m_layoutMargins.clear();
        collapseLayout(master);
      }
    }
    break;
  case CollapseExpandButton::State::EXPANDED:
    for (QObject *o : children) {
      QWidget *w = qobject_cast<QWidget *>(o);
      if (w != nullptr) {
        w->setVisible(true);

        continue;
      }

      if (o == master)
        expandLayout(master);

    }
    break;
  }
}


void CollapsibleGroupBox::collapseLayout(QLayout *layout)
{
  for (QObject *o : layout->children()) {
    QLayout *l= qobject_cast<QLayout *>(o);
    if (l == nullptr)
      continue;

    collapseLayout(l);
  }
  if (m_layoutMargins.contains(layout))
    return;

  QMargins m = layout->contentsMargins();
  m_layoutMargins[layout] = m;
  layout->setContentsMargins(0, 0, 0, 0);
}

void CollapsibleGroupBox::expandLayout(QLayout *layout)
{
  for (QObject *o : layout->children()) {
    QLayout *l = qobject_cast<QLayout *>(o);
    if (l == nullptr)
      continue;

    if (m_layoutMargins.contains(l))
      expandLayout(l);
  }
  if (m_layoutMargins.contains(layout)) {
    QMargins m = m_layoutMargins[layout];
    layout->setContentsMargins(m);
  }
}

【问题讨论】:

  • 您是否尝试过隐藏布局而不触及其他任何内容?这应该完成隐藏所有孩子的工作。
  • 另外,您是否担心折叠的高度或宽度?有截图吗?
  • 此时我只关心身高。 QLayout 似乎没有 setVisible() 方法。
  • 至于截图:不同大小的折叠组框:imgur.com/tg4jSAw Designer HVL 中显示的这些小部件:imgur.com/NqqvJwq Peak:imgur.com/6AvPyye
  • 您是否尝试在布局末尾添加拉伸?这将强制组合框达到它们的最小高度。

标签: c++ qt collapse expand qgroupbox


【解决方案1】:

这是由于您提到的分组框中的垫片。将它们的高度设置为零;当 groupbox 展开时,它不应该影响他们的行为。

【讨论】:

    【解决方案2】:

    我无法解释组合框明显不可重现的高度。但你也可以这样做:

    void CollapsibleGroupBox::onVisibilityChanged(bool state)
    {
        switch (state) {
        case true:
            m_originalMaxHeight = this->maximumHeight();
            this->setMaximumHeight(this->fontMetrics().height());
            break;
        case false:
            this->setMaximumHeight(m_originalMaxHeight);
            break;
        }
    }
    

    并向包含组框的布局添加一个拉伸。动态添加或删除小部件时,它可以很好地调整大小。

    【讨论】:

      【解决方案3】:

      前段时间我回到了我的旧代码,看来我终于想出了一个合理的解决方案。完整代码如下:

      ** CollapsibleGroupBox.h **

      #ifndef COLLAPSIBLEGROUPBOX_H
      #define COLLAPSIBLEGROUPBOX_H
      
      #include <QGroupBox>
      #include <QMap>
      #include <QMargins>
      #include <QPair>
      
      class QResizeEvent;
      class CollapseExpandButton;
      class QSpacerItem;
      
      class CollapsibleGroupBox : public QGroupBox
      {
      public:
        explicit CollapsibleGroupBox(QWidget *parent = nullptr);
      
      protected:
        void resizeEvent(QResizeEvent *);
      
      private:
        void resizeCollapseButton();
        void collapseLayout(QLayout *layout);
        void collapseSpacer(QSpacerItem *spacer);
        void expandLayout(QLayout *layout);
        void expandSpacer(QSpacerItem *spacer);
      
        CollapseExpandButton *m_clExpButton;
        QMap<const void *, QMargins> m_layoutMargins;
        QMap<const void *, QPair<QSize, QSizePolicy>> m_spacerSizes;
      
      private slots:
        void onScreenChanged();
        void onVisibilityChanged();
      
      };
      
      #endif // COLLAPSIBLEGROUPBOX_H
      

      ** CollapsibleGroupBox.cpp **

      #include "collapsiblegroupbox.h"
      #include "collapseexpandbutton.h"
      #include <QApplication>
      #include <QGuiApplication>
      #include <QLayout>
      #include <QResizeEvent>
      #include <QScreen>
      #include <QStyle>
      #include <QTimer>
      #include <QWindow>
      #include <cassert>
      #include <cmath>
      
      inline
      QWindow *findWindowForWidget(const QWidget *widget)
      {
        for (;;) {
          QWindow *wh = widget->window()->windowHandle();
          if (wh != nullptr)
            return wh;
      
          widget = qobject_cast<const QWidget *>(widget->parent());
          if (widget == nullptr)
            return nullptr;
        }
      }
      
      inline
      QScreen * findScreenForWidget(const QWidget *widget)
      {
        for (;;) {
          QWindow *wh = widget->window()->windowHandle();
          if (wh != nullptr) {
            QScreen *scr = wh->screen();
            if (scr != nullptr)
              return scr;
          }
      
          widget = qobject_cast<const QWidget *>(widget->parent());
          if (widget == nullptr)
            return nullptr;
        }
      }
      
      CollapsibleGroupBox::CollapsibleGroupBox(QWidget *parent) :
        QGroupBox(parent)
      {
        m_clExpButton = new CollapseExpandButton(this);
      
        connect(m_clExpButton, &CollapseExpandButton::clicked, this, &CollapsibleGroupBox::onVisibilityChanged);
      
        QTimer::singleShot(0, this, [this] {
          auto wh = findWindowForWidget(this);
          if (wh != nullptr)
            connect(wh, &QWindow::screenChanged, this, &CollapsibleGroupBox::onScreenChanged);
        });
      
        QTimer::singleShot(0, this, &CollapsibleGroupBox::resizeCollapseButton);
      }
      
      void CollapsibleGroupBox::collapseLayout(QLayout *lay)
      {
        assert(!m_layoutMargins.contains(lay));
      
        const int cnt = lay->count();
        for (int idx = 0; idx < cnt; idx++) {
          auto lit = lay->itemAt(idx);
      
          if (lit->widget()) {
            auto w = lit->widget();
            if (w != m_clExpButton)
              w->setVisible(false);
          }
          else if (lit->spacerItem())
            collapseSpacer(lit->spacerItem());
          else if (lit->layout())
            collapseLayout(lit->layout());
        }
      
        m_layoutMargins[lay] = lay->contentsMargins();
        lay->setContentsMargins(0, 0, 0, 0);
      }
      
      void CollapsibleGroupBox::collapseSpacer(QSpacerItem *spacer)
      {
        assert(!m_spacerSizes.contains(spacer));
      
        m_spacerSizes[spacer] = {spacer->sizeHint(), spacer->sizePolicy()};
        spacer->changeSize(0, 0);
      }
      
      void CollapsibleGroupBox::expandLayout(QLayout *lay)
      {
        assert(m_layoutMargins.contains(lay));
      
        const int cnt = lay->count();
        for (int idx = 0; idx < cnt; idx++) {
          auto lit = lay->itemAt(idx);
      
          if (lit->widget())
            lit->widget()->setVisible(true);
          else if (lit->spacerItem())
            expandSpacer(lit->spacerItem());
          else if (lit->layout())
            expandLayout(lit->layout());
        }
      
        lay->setContentsMargins(m_layoutMargins[lay]);
      }
      
      void CollapsibleGroupBox::expandSpacer(QSpacerItem *spacer)
      {
        assert(m_spacerSizes.contains(spacer));
      
        const auto &sz = m_spacerSizes[spacer].first;
        const auto &pol = m_spacerSizes[spacer].second;
      
        spacer->changeSize(sz.width(), sz.height(), pol.horizontalPolicy(), pol.verticalPolicy());
      }
      
      void CollapsibleGroupBox::onScreenChanged()
      {
        resizeCollapseButton();
      }
      
      void CollapsibleGroupBox::onVisibilityChanged()
      {
        assert(this->layout() != nullptr);
      
        CollapseExpandButton::State s = m_clExpButton->state();
      
        switch (s) {
        case CollapseExpandButton::State::COLLAPSED:
          m_layoutMargins.clear();
          m_spacerSizes.clear();
      
          collapseLayout(this->layout());
          break;
        case CollapseExpandButton::State::EXPANDED:
          expandLayout(this->layout());
          break;
        }
      }
      
      void CollapsibleGroupBox::resizeCollapseButton()
      {
        const QScreen *scr = findScreenForWidget(this);
      
        if (scr == nullptr)
          return;
      
        const auto &size = this->size();
      
      #ifdef Q_OS_WIN
        qreal baseSize = 15.0;
        int yOffset = 5;
      #else
        qreal baseSize = 22.0;
        int yOffset = 0;
      #endif
      
        if (scr == nullptr)
          return;
      
        if (QString::compare(QApplication::style()->objectName(), "fusion") == 0)
          baseSize = 15.0;
      
        const qreal dpi = scr->logicalDotsPerInchX();
        const qreal btnSize = floor((baseSize * dpi / 96.0) + 0.5);
      
        m_clExpButton->setGeometry(size.width() - btnSize, yOffset, btnSize, btnSize);
      }
      
      void CollapsibleGroupBox::resizeEvent(QResizeEvent *)
      {
        resizeCollapseButton();
      }
      

      这似乎完全按照我的预期折叠并恢复了盒子。

      【讨论】:

      • 能否请您同时提供collapseexpandbutton的缺失来源
      • 您可以实现一个继承自 QToolButton 的 CollapseExpandButton(可检查)。在那里您可以存储状态(COLLAPSED 或 EXPANDED),连接到 clicked() 信号并更新插槽中的内部状态,如下所示: void CollapseExpandButton::onClicked(bool checked) { _state = checked ?展开:折叠;块信号(真);发射(点击(选中));块信号(假); } 或者,如果您愿意,您可以完全放弃 State,直接使用 CollapsibleGroupBox::onVisibilityChanged() 中的“m_clExpButton->state()”中的“m_clExpButton->isChecked()”。
      猜你喜欢
      • 1970-01-01
      • 2015-12-05
      • 1970-01-01
      • 1970-01-01
      • 2019-07-03
      • 2021-10-27
      • 2018-12-13
      • 1970-01-01
      • 2018-12-24
      相关资源
      最近更新 更多