Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Prevent QGridLayout from stagnating in size while inside of QScrollArea



  • I have a QGridLayout containing a grid of custom labels that act as buttons. It is absolutely crucial that the layout never exceeds it's minimum size and that the contents stay the same fixed size.

    The contents of the grid are scaled in multiples by the user. It does not use the layout to resize (i.e., resizing is done by calling setFixedSize()).

    Whenever the layout is contained in a QScrollArea, once it reaches certain sizes, it will either:

    • Not expand large enough
      af2d9920-0b00-4214-b2be-e37bf0097ed1-image.png
      You can tell that's the case because the bottom and right corners are clipping behind the next.

    • Expand too large
      c7807c50-406e-459e-ab76-a60d87e634ef-image.png
      You can tell that's the case because the statically sized labels are not right up against each-other.

    In both of these scenarios, there is excess space on the left and top.
    I have set layout spacing & contents margins to 0 on all sides.
    The widget directly containing the layout has its H&V size policies set to Fixed.

    This is how it should look:
    (When too large for area)
    2d4bc094-5310-4eec-9e44-c7dbd65f76e4-image.png

    (When too small for area)
    df37f3d9-6bfe-4dce-82cb-a4523e560fa7-image.png

    Scroll area variables:

    • Size policies: Expanding
    • Min, max, base sizes: 0
    • Size Adjust Policy: Ignored
    • Widget Resizable: Yes

    I am willing to make a custom QLayout for this. I am just not sure where to start.
    Any help is appreciated.


  • Lifetime Qt Champion

    Hi,

    Is it some sort of game view ?



  • Something like that, yeah. It is currently a display & selector for a "tileset", which is just a grid of squares to determine the tiles a map in the game would use (each map can only use 1 tileset). Most of the code for this can be re-implemented into a view & editor for the map as well. It's not a game, it's an editor for the game.

    If you're curious, I overrode the paint events for both the containing widget and all of its children (which are of the same type). If there's anything here that I could do better, please let me know.

    void TilesetArea::paintEvent(QPaintEvent *)
    {
      QPainter p(this);
      p.setBrush(QPixmap(QCoreApplication::applicationDirPath() + "/resource/image/tileset-background.png").scaled(GameGlobal::tilesetResolution * EddyGlobal::tilesetScale, GameGlobal::tilesetResolution * EddyGlobal::tilesetScale));
      p.setPen(Qt::transparent);
      p.drawRect(this->rect());
    }
    
    
    
    void TileLabel::paintEvent(QPaintEvent *evt)
    {
      setFixedSize(GameGlobal::tilesetResolution * EddyGlobal::tilesetScale, GameGlobal::tilesetResolution * EddyGlobal::tilesetScale);
      QPainter p(this);
      QPen pen;
      QPixmap scaled = this->pixmap(Qt::ReturnByValue).copy().scaled(GameGlobal::tilesetResolution * EddyGlobal::tilesetScale, GameGlobal::tilesetResolution * EddyGlobal::tilesetScale);
      p.setBrush(scaled);
      if(EddyGlobal::tilesetGrid)
        {
          pen.setWidth(2);
          pen.setColor(Qt::darkGray);
        }
      else
        {
          pen.setWidth(0);
          pen.setColor(Qt::transparent);
        }
      if(mouseOver)
        {
          QImage temp = scaled.toImage();
          temp.invertPixels(QImage::InvertRgb);
          p.setBrush(temp);
        }
      p.setPen(pen);
      p.drawRect(this->rect());
    }
    

    TileLabel is each of the tiles individually. TilesetArea is the entire field (with the gray checkered background).

    Another member that might be relevant:

    void TilesetArea::initialize(QString pathToTileset)
    {
      QFile imageFile(pathToTileset);
      imageFile.open(QFile::ReadOnly);
      QPixmap image(pathToTileset);
      imageFile.close();
    
      //EddyGlobal::fileWatcher->addPath(pathToTileset);
    
      QGridLayout *layout = new QGridLayout;
    
      if((((image.width() * image.height()) % GameGlobal::tilesetResolution) != 0))
        qDebug() << "Source tileset image does not match tileset resolution (" << GameGlobal::tilesetResolution <<"). If you're sure it does, make sure there is no excess space.";
      else
        {
          int tilesX = image.width() / GameGlobal::tilesetResolution;
          int tilesY = image.height() / GameGlobal::tilesetResolution;
    
          int currentX = 0;
          int currentY = 0;
          for(int i = tilesX * tilesY; i > 0; i--)
            {
              TileLabel *tile = new TileLabel;
    
              QObject::connect(this, SIGNAL(tilesetScaleChanged()), tile, SLOT(repaint()));
              QObject::connect(this, SIGNAL(tilesetGridChanged()), tile, SLOT(repaint()));
              tile->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
              tile->setPixmap(QPixmap(image.copy(currentX, currentY, GameGlobal::tilesetResolution, GameGlobal::tilesetResolution)).scaled(GameGlobal::tilesetResolution * EddyGlobal::tilesetScale, GameGlobal::tilesetResolution* EddyGlobal::tilesetScale));
              layout->addWidget(tile, currentY / GameGlobal::tilesetResolution, currentX / GameGlobal::tilesetResolution);
              if(currentX + GameGlobal::tilesetResolution == image.width())
                {
                  currentY = currentY + GameGlobal::tilesetResolution;
                  currentX = 0;
                }
              else
                {
                  currentX = currentX + GameGlobal::tilesetResolution;
                }
            }
        }
      layout->setSpacing(0);
      layout->setContentsMargins(0, 0, 0, 0);
      this->setLayout(layout);
      return;
    }
    

  • Lifetime Qt Champion

    Did you consider the Graphics View Framework ?

    It looks more suited to your project.



  • @SGaist I will take a look. :)


Log in to reply