QScrollArea as a transparent wrapper



  • Hi.

    I've already asked this question on Stackoverflow, but this place is probably better tailored to the problem.

    I want QScrollArea to act as a transparent wrapper for a window's contents, but haven't had a lot of success in doing so.

    First, some boring code as a MWE.

    qscrollarea_test.pro:

    HEADERS = qscrollarea_test.hpp
    SOURCES = qscrollarea_test.cpp main.cpp
    CONFIG += qt debug
    QT += gui widgets
    

    main.cpp:

    #include "qscrollarea_test.hpp"
    
    int main (int argc, char **argv) {
      QApplication app (argc, argv);
    
      scroll_area_test widget (true);
      widget.show();
    
      return (app.exec ());
    }
    

    qscrollarea_test.hpp:

    #pragma once
    #ifndef QSCROLLAREA_TEST_HPP
    #define QSCROLLAREA_TEST_HPP
    
    #include <QVBoxLayout>
    #include <QScrollArea>
    #include <QWidget>
    #include <QPushButton>
    #include <QApplication>
    
    class scroll_area_test : public QWidget {
      Q_OBJECT
    
      public:
        scroll_area_test (bool want_scrollarea = true);
        void scroll_area_test_normal ();
        void scroll_area_test_qscrollarea ();
    };
    
    #endif /* !defined (QSCROLLAREA_TEST_HPP) */
    

    qscrollarea_test.cpp:

    #include <iostream>
    #include <QDebug>
    
    #include "qscrollarea_test.hpp"
    
    scroll_area_test::scroll_area_test (bool want_scrollarea) {
      if (want_scrollarea) {
        scroll_area_test::scroll_area_test_qscrollarea ();
      }
      else {
        scroll_area_test::scroll_area_test_normal ();
      }
    }
    
    void scroll_area_test::scroll_area_test_normal () {
      QVBoxLayout *top_layout = new QVBoxLayout (this);
    
      QWidget *contents = new QWidget (this);
      top_layout->addWidget (contents);
    
      QVBoxLayout* inner_layout = new QVBoxLayout (contents);
    
      for (size_t i = 0; i < 25; ++i) {
        QPushButton *tmp_button = new QPushButton (this);
        inner_layout->addWidget (tmp_button);
      }
    
      qDebug () << "total size (hint): " << this->size () << " - " << this->sizeHint ();
      qDebug () << "layout size hint: " << top_layout->sizeHint ();
      qDebug () << "contents size (hint): " << contents->size () << " - " << contents->sizeHint ();
    }
    
    void scroll_area_test::scroll_area_test_qscrollarea () {
      QVBoxLayout *top_layout = new QVBoxLayout ();
    
      QScrollArea *scrolling_area = new QScrollArea ();
      scrolling_area->setWidgetResizable (true);
      scrolling_area->setFocusPolicy (Qt::NoFocus);
      top_layout->addWidget (scrolling_area);
    
      QWidget *contents = new QWidget ();
      contents->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    
      QVBoxLayout* inner_layout = new QVBoxLayout ();
      inner_layout->setSizeConstraint (QLayout::SetMinimumSize);
    
      for (size_t i = 0; i < 25; ++i) {
        QPushButton *tmp_button = new QPushButton ();
        inner_layout->addWidget (tmp_button);
      }
    
      contents->setLayout (inner_layout);
      this->setLayout (top_layout);
    
      scrolling_area->setWidget (contents);
    
      top_layout->addWidget (scrolling_area);
    
      //this->resize (contents->size ());
    
      qDebug () << "total size (hint): " << this->size () << " - " << this->sizeHint ();
      qDebug () << "layout size hint: " << top_layout->sizeHint ();
      qDebug () << "contents size (hint): " << contents->size () << " - " << contents->sizeHint ();
      qDebug () << "scroll area size (hint): " << scrolling_area->size () << " - " << scrolling_area->sizeHint ();
    }
    

    Without QScrollArea as a wrapper, the window is shown as such (and this is actually my expected result):

    Window without contents wrapped in QScrollArea

    Also, the size debug output sounds (more or less) reasonable:

    total size (hint):  QSize(640, 480)  -  QSize(84, 809)
    layout size hint:  QSize(84, 809)
    contents size (hint):  QSize(100, 30)  -  QSize(62, 787)
    

    Note how the window's size() looks odd (and does not reflect its actual size), but sizeHint() is saner. The same goes for contents->size() and contents->sizeHint().

    Now, changing to the code where a QScrollArea is used, leads to this "output":

    0_1510493991841_Wrapped_in_QScrollArea.png

    total size (hint):  QSize(640, 480)  -  QSize(92, 430)
    layout size hint:  QSize(92, 430)
    contents size (hint):  QSize(638, 787)  -  QSize(62, 787)
    scroll area size (hint):  QSize(640, 480)  -  QSize(64, 408)
    

    This isn't quite what I expected - but why? Given that I have enough screen space, I would have expected the window to be sized and look just like when not using QScrollArea as a wrapper, but this is obviously not true.

    I am fully aware that this may contradict using QScrollArea as a wrapper in the first place. The general idea, though, is to keep the window correctly sized even on displays with lower resolutions. Its size should roughly be min (<contents size (+ margins etc.)>, <usable display size - window position>). In the first case, no scrolling will be necessary at all, so users should not even notice the QScrollArea wrapper, while in the second case, the window will use the available size without overflowing display borders, with scrollable contents due to the QScrollArea wrapper.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Which version of Qt are you using ?



  • Of course, I should have mentioned that. This is with Qt 5.9.2, but the code can be equally compiled against 4.8.7 and the behavior is exactly the same. I generally test with both. Legacy platforms and such.


  • Lifetime Qt Champion

    Try adding top_layout->setContentsMargins(0, 0, 0 , 0);

    I think it should give you the result you are looking for.



  • @SGaist said in QScrollArea as a transparent wrapper:

    top_layout->setContentsMargins(0, 0, 0 , 0);

    Uhm, no, that doesn't change anything.

    It actually can't, either, since it's not the top layout resizing the contents (well, not in this case at least, since it doesn't specify a maximum size), but QScrollArea itself.

    According to the source code, QScrollArea overrides sizeHint() and does some bounding calculations - but I can't explain the behavior based upon this, since the bounding merely makes sure that a minimum size is respected. It doesn't constrain to a maximum size.

    The sizeHint returned by QScrollArea should be at least as big as either the sizeHint or the size returned by its internal widget, but that's clearly not the case, judging from my debug output. It's smaller than that. Well, at least the height is.



  • @Ionic
    I am only a newbie, not an expert like @SGaist . I have started using QScrollLayouts, and find them most confusing compared to what I am used from elsewhere. I probably don't have a clue what I'm talking about here but can't resist sticking my nose in, so having said that, here's what I would look at if I were you (if they're all left-field, don't be surprised!):

    • Try altering the order of your adds, e.g. add the scroll area to the layout before you set its content. Any difference?

    • You print out all the sizes & size hints right at the end. Try printing various ones as you go along, before & after adding/setting things. Try to identify exactly which lines of code alter which ones.

    • Try with a simpler content, such as just a single QWidget::setFixedSize(). (Probably irrelevant, but just in case.)

    • Is the discussion in https://stackoverflow.com/questions/14980620/qt-layout-resize-to-minimum-after-widget-size-changes useful, even if it's not the same as your problem? Need to use resize() on the right layout/widget?


  • Lifetime Qt Champion

    @Ionic there were two top_layout in your example IIRC, which one did you modify ?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.