Динамическое изменение ToolButtonStyle QToolButtons в зависимости от размера окна

Я создал класс виджетов (в C++) ToolTray, который в основном состоит из нескольких QToolButton, добавленных в QHboxLayout. Эти кнопки используются пользователем для таких операций, как Save, Open и т. д. Первоначально кнопки добавляются с toolButtonStyle, установленным на Qt::ToolButtonTextBesideIcon.

Я хочу изменить toolButtonStyle на Qt::ToolButtonIconOnly в resizeEvent, если нового размера недостаточно для отображения текста и значков QToolButton. См. рисунок ниже:

введите здесь описание изображения

Если размер окна изменен и нового размера достаточно для отображения текста и значков всех кнопок QToolButton, то toolButtonStyle следует изменить обратно на Qt::ToolButtonTextBesideIcon.

введите здесь описание изображения

Я попытался добиться этого с помощью следующего кода:

void ToolTray::resizeEvent(QResizeEvent *event)
{
    int totalWidth = 0;
    bool mode = runBtn_->toolButtonStyle() == Qt::ToolButtonStyle::ToolButtonIconOnly;
    // Mode => True => For ICON Only
    // Mode => False => For ICON + Text
    for (auto btn: toolBtns_) {
        if (btn->isVisible())
            totalWidth += btn->size().width();
    }

    qDebug() << "Total Width: " << totalWidth ;
    qDebug() << "Event Size: " << event->size() << " Old size " << event->oldSize();

    if (event->oldSize().isEmpty()) // Ignore ResizeEvent for QSize(-1,-1)
        return;

    if (mode) { // Already Small
        if (event->size().width() < preferedFullWidth_)
            return;
        for (auto btn: toolBtns_) {
            if (btn == moreBtn_)
                continue;
            btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
        }
        return;
    }
    // The QToolButtons are Text Beside Icon
    if (event->size().width() >= totalWidth)
        return;

    qDebug() << "Here";
    for (auto btn: toolBtns_)
        btn->setToolButtonStyle(Qt::ToolButtonIconOnly);
    preferedFullWidth_ = totalWidth;
}

Однако я не могу добиться того, чего хотел:

  1. Всякий раз, когда я пытаюсь уменьшить размер окна, текст QToolButton сначала начинает обрезаться, и после некоторого уменьшения размера ToolButtonStyle изменяется на Qt::ToolButtonIconOnly. Я не хочу, чтобы произошло отсечение.

  2. Я также хочу добавить немного запаса (или гистерезиса) в свою реализацию. Например, как только QToolButton изменится на Qt::ToolButtonIconOnly при определенной ширине, тогда ширина должна быть больше, чем PreferredFullWidth_ + определенный запас, чтобы переключиться обратно на Qt::ToolButtonTextBesideIcon.


person abhiarora    schedule 12.09.2019    source источник
comment
Вам не понравился мой ответ?   -  person Maxim Paperno    schedule 15.10.2019


Ответы (1)


Панели инструментов кажутся темой на этой неделе... :)

Таким образом, у вас в основном есть правильная идея, но сложная часть заключается в определении необходимого размера. К сожалению, QToolBar макет полностью private, поэтому нам приходится разбираться во всем самостоятельно (хотя он наследуется от QLayout, вы не можете получить его экземпляр через QToolBar::layout()).

Эта реализация довольно проста и, вероятно, не будет обрабатывать все случаи (я тестировал только базовые действия, без пользовательских виджетов или чего-то подобного), но для этого она работает (проверено на Windows и Linux с различными стилями).

ДОБАВЛЕНО: Я понимаю, что вы не просили конкретно QToolBar, но это то, что вы, по сути, описали... Я не уверен, зачем заново изобретать это колесо (QToolBar можно разместить в любой раскладке , не обязательно должно быть в главном окне), но если бы вы реализовали свою собственную версию, я думаю, что многое из этого примера все еще применимо. Я также лично почти всегда использовал бы QActions для запуска событий пользовательского интерфейса (вместо кнопок), поскольку они могут быть назначены любому количеству элементов пользовательского интерфейса (панель инструментов, панель меню, контекстное меню, ярлыки окон и т. д.).

Добавление поля/гистерезиса оставлено читателю в качестве упражнения... :) Я не думаю, что оно нужно, но вы можете добавить m_expandedSize в initSizes() с произвольным запасом.

Сворачивание панели инструментов

#include <QtWidgets>

class CollapsingToolBar : public QToolBar
{
    Q_OBJECT
  public:
    explicit CollapsingToolBar(QWidget *parent = nullptr) : CollapsingToolBar(QString(), parent) {}

    explicit CollapsingToolBar(const QString &title, QWidget *parent = nullptr) :
      QToolBar(title, parent)
    {
      initSizes();
      // If icon sizes change we need to recalculate all the size hints, but we need to wait until the buttons have adjusted themselves, so we queue the update.
      connect(this, &QToolBar::iconSizeChanged, [this](const QSize &) {
        QMetaObject::invokeMethod(this, "recalcExpandedSize", Qt::QueuedConnection);
      });
      // The drag handle can mess up our sizing, update preferred size if it changes.
      connect(this, &QToolBar::movableChanged, [this](bool movable) {
        const int handleSz = style()->pixelMetric(QStyle::PM_ToolBarHandleExtent, nullptr, this);;
        m_expandedSize = (movable ? m_expandedSize + handleSz : m_expandedSize - handleSz);
        adjustForSize();
      });
    }

  protected:

    // Monitor action events to keep track of required size.
    void actionEvent(QActionEvent *e) override
    {
      QToolBar::actionEvent(e);

      int width = 0;
      switch (e->type())
      {
        case QEvent::ActionAdded:
          // Personal pet-peeve... optionally set buttons with menus to have instant popups instead of splits with the main button doing nothing.
          //if (QToolButton *tb = qobject_cast<QToolButton *>(widgetForAction(e->action())))
          //    tb->setPopupMode(QToolButton::InstantPopup);
          //Q_FALLTHROUGH;
        case QEvent::ActionChanged:
          width = widthForAction(e->action());
          if (width <= 0)
            return;

          if (e->type() == QEvent::ActionAdded || !m_actionWidths.contains(e->action()))
            m_expandedSize += width + m_spacing;
          else
            m_expandedSize = m_expandedSize - m_actionWidths.value(e->action()) + width;
          m_actionWidths.insert(e->action(), width);
          break;

        case QEvent::ActionRemoved:
          if (!m_actionWidths.contains(e->action()))
            break;
          width = m_actionWidths.value(e->action());
          m_expandedSize -= width + m_spacing;
          m_actionWidths.remove(e->action());
          break;

        default:
          return;
      }
      adjustForSize();
    }

    bool event(QEvent *e) override
    {
      // Watch for style change
      if (e->type() == QEvent::StyleChange)
        recalcExpandedSize();
      return QToolBar::event(e);
    }

    void resizeEvent(QResizeEvent *e) override
    {
      adjustForSize();
      QToolBar::resizeEvent(e);
    }

  private slots:

    // Here we do the actual switching of tool button style based on available width.
    void adjustForSize()
    {
      int availableWidth = contentsRect().width();
      if (!isVisible() || m_expandedSize <= 0 || availableWidth <= 0)
        return;

      switch (toolButtonStyle()) {
        case Qt::ToolButtonIconOnly:
          if (availableWidth > m_expandedSize)
            setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
          break;

        case Qt::ToolButtonTextBesideIcon:
          if (availableWidth <= m_expandedSize)
            setToolButtonStyle(Qt::ToolButtonIconOnly);
          break;

        default:
          break;
      }
    }

    // Loops over all previously-added actions and re-calculates new size (eg. after icon size change)
    void recalcExpandedSize()
    {
      if (m_actionWidths.isEmpty())
        return;
      initSizes();
      int width = 0;
      QHash<QAction *, int>::iterator it = m_actionWidths.begin();
      for ( ; it != m_actionWidths.end(); ++it) {
        width = widthForAction(it.key());
        if (width <= 0)
          continue;
        m_expandedSize += width + m_spacing;
        it.value() = width;
      }
      adjustForSize();
    }

  private:
    void initSizes()
    {
      // Preload some sizes based on style settings.
      // This is the spacing between items
      m_spacing = style()->pixelMetric(QStyle::PM_ToolBarItemSpacing, nullptr, this);
      // Size of a separator
      m_separatorWidth = style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, nullptr, this);
      // The layout margins (we can't even get the private QToolBarLayout via layout() so we figure it out like it does)
      m_expandedSize = (style()->pixelMetric(QStyle::PM_ToolBarItemMargin, nullptr, this) + style()->pixelMetric(QStyle::PM_ToolBarFrameWidth, nullptr, this)) * 2;
      // And the size of the drag handle if we have one
      if (isMovable())
        m_expandedSize += style()->pixelMetric(QStyle::PM_ToolBarHandleExtent, nullptr, this);
    }

    int widthForAction(QAction *action) const
    {
      // Try to find how wide the action representation (widget/separator) is.
      if (action->isSeparator())
        return m_separatorWidth;

      if (QToolButton *tb = qobject_cast<QToolButton *>(widgetForAction(action))) {
        const Qt::ToolButtonStyle oldStyle = tb->toolButtonStyle();
        // force the widest size
        tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
        const int width = tb->sizeHint().width();
        tb->setToolButtonStyle(oldStyle);
        return width;
      }

      if (const QWidget *w = widgetForAction(action))
        return w->sizeHint().width();

      return 0;
    }

    int m_expandedSize = -1;   // The maximum size we need with all buttons expanded and allowing for margins/etc
    int m_spacing = 0;         // Layout spacing between items
    int m_separatorWidth = 0;  // Width of separators
    QHash<QAction *, int> m_actionWidths;  // Use this to track action additions/removals/changes
};

Тест/демонстрация


// An XPM icon ripped from QCommonStyle
static const char * const info_xpm[]={
"32 32 5 1",
". c None",
"c c #000000",
"* c #999999",
"a c #ffffff",
"b c #0000ff",
"...........********.............",
"........***aaaaaaaa***..........",
"......**aaaaaaaaaaaaaa**........",
".....*aaaaaaaaaaaaaaaaaa*.......",
"....*aaaaaaaabbbbaaaaaaaac......",
"...*aaaaaaaabbbbbbaaaaaaaac.....",
"..*aaaaaaaaabbbbbbaaaaaaaaac....",
".*aaaaaaaaaaabbbbaaaaaaaaaaac...",
".*aaaaaaaaaaaaaaaaaaaaaaaaaac*..",
"*aaaaaaaaaaaaaaaaaaaaaaaaaaaac*.",
"*aaaaaaaaaabbbbbbbaaaaaaaaaaac*.",
"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**",
"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**",
"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**",
"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**",
"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**",
".*aaaaaaaaaaabbbbbaaaaaaaaaac***",
".*aaaaaaaaaaabbbbbaaaaaaaaaac***",
"..*aaaaaaaaaabbbbbaaaaaaaaac***.",
"...caaaaaaabbbbbbbbbaaaaaac****.",
"....caaaaaaaaaaaaaaaaaaaac****..",
".....caaaaaaaaaaaaaaaaaac****...",
"......ccaaaaaaaaaaaaaacc****....",
".......*cccaaaaaaaaccc*****.....",
"........***cccaaaac*******......",
"..........****caaac*****........",
".............*caaac**...........",
"...............caac**...........",
"................cac**...........",
".................cc**...........",
"..................***...........",
"...................**..........."};

class MainWindow : public QMainWindow
{
    Q_OBJECT
  public:
    MainWindow(QWidget *parent = nullptr)
      : QMainWindow(parent)
    {
      QToolBar* tb = new CollapsingToolBar(this);
      tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
      tb->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);

      QIcon icon = QIcon(QPixmap(info_xpm));
      for (int i=0; i < 6; ++i)
        tb->addAction(icon, QStringLiteral("Action %1").arg(i));

      addToolBar(tb);
      show();

      // Adding another action after show() may collapse all the actions if the new toolbar preferred width doesn't fit the window.
      // Only an issue if the toolbar size hint was what determined the window width to begin with.
      //tb->addAction(icon, QStringLiteral("Action After"));

      // Test setting button style after showing (comment out the one above)
      //tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);

      // Test changing icon size after showing.
      //tb->setIconSize(QSize(48, 48));

      // Try this too...
      //tb->setMovable(false);
    }
};

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
  //QApplication::setStyle("Fusion");
  //QApplication::setStyle("windows");

  MainWindow w;
  return app.exec();
}

введите здесь описание изображения введите здесь описание изображения

person Maxim Paperno    schedule 13.09.2019