PDA

View Full Version : How to update size of nested QWidget?



Maryu
24th December 2021, 17:40
I'm using Qt 5.15 with C++17. I have a hierarchy of Widgets like hinted in the title:


MainWindow : QMainWindow
->centralWidget() = QScrollArea
->widget() = QWidget
->layout() = QVBoxLayout
->children() = [
InnerWidget,
InnerWidget,
InnerWidget,
...
]

with
InnerWidget.children() = [ QLabel, QLabel ].
I have set
InnerWidget::setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum) inside InnerWidget's constructor.

The QLabels inside InnerWidget are initialized with some QPixmap like


label->setPixmap(img);
label->setFixedSize(img.size());

_________________


This all works fine, however now I added an option to zoom all the pixmaps to the MainWindow's menubar. I connect to a signal MainWindow::zoom_inner(.., which itself calls InnerWidget::zoom(.. on every InnerWidget, which resizes the images / labels like this:


auto scaled = img.scaled(img.size() * factor);
label->setPixmap(scaled);
label->setFixedSize(scaled.size());


Finally I update the size of the MainWindow by finding the maximum InnerWidget.frameGeometry().width() (= inner_max_width) and calling


void MainWindow::zoom_inner() {
// ...
setMaximumWidth(inner_max_width + centralWidget()->verticalScrollBar()->width() + 1);
resize(maximumWidth(), size().height());
}

_________________

Now after calling MainWindow::zoom(.., the window seems to resize to the correct size, as do the QLabel's inside the InnerWidget's. However, the InnerWidget's themselves do not resize at all, they just stay the same size as before and I have no idea why. I tried calling many combinations of


adjustSize();
update();
layout()->update();
updateGeometry();

on any of the MainWindow,QScrollArea,InnerWidget but nothing happens.. even calling
InnerWidget::resize(new_size) has no effect. Clearly InnerWidget::sizeHint() gives the correct value, because the MainWindow size after MainWindow::zoom(.. is correct. So why do the InnerWidget's refuse to resize to fit the QLabel's, even though sizePolicy is set to QSizePolicy::Minimum?

This seems like some sort of misunderstanding on my part so I'm hoping the answer is simple and doesn't need a MWE. I've tried the docs but couldn't find a solution.

d_stranz
24th December 2021, 23:41
sizePolicy is set to QSizePolicy::Minimum

This is telling the VBox layout that the widget's sizes are fixed at their minimum sizes. You need a size policy that includes "Expanding" or use "Preferred".

Maryu
25th December 2021, 14:21
I do want the InnerWidget's to be fixed at their minimum size, but the minimum size itself should update after resizing the QPixmap's.
This doesn't seem to happen, even though InnerWidget::sizeHint clearly updates correctly, since the MainWindow is resized correctly.

Seems I cannot edit my post, but I made a MWE:


include/NestedWidgetResizeIssue_MWE.hh


#include <vector>

#include <QtGui/QPixmap>
#include <QtWidgets/QAction>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QScrollArea>
#include <QtWidgets/QWidget>
#include <QtWidgets/QFrame>

class InnerWidget : public QFrame {

QPixmap m_img_1;
QPixmap m_img_2;
QLabel* m_label_1;
QLabel* m_label_2;

static QPixmap create_black_img();
static void update_img_label(const QPixmap& img, QLabel* label);
static void init_img_label(QPixmap& img, QLabel*& label);
static void zoom_img_label(double factor, QPixmap& img, QLabel* label);

public:
InnerWidget();

void zoom(double factor);
};

// --------------------------------------------------

class MainWindow : public QMainWindow {
Q_OBJECT

QMenu* m_edit_menu;
QAction* m_zoom_action;

QScrollArea* m_central;
QWidget* m_main;

std::vector<InnerWidget*> m_entries;

void update_max_width();

private slots:
void zoom_inner();

public:
MainWindow();
};



src/NestedWidgetResizeIssue_MWE.cc


#include <cmath>

#include <QtGui/QPainter>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QScrollBar>

#include <NestedWidgetResizeIssue_MWE.hh>

InnerWidget::InnerWidget() {
setFrameShape(QFrame::Panel);
auto* layout = new QHBoxLayout{};

init_img_label(m_img_1, m_label_1);
init_img_label(m_img_2, m_label_2);
layout->addWidget(m_label_1);
layout->addWidget(m_label_2);

setLayout(layout);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
adjustSize();
}

QPixmap InnerWidget::create_black_img() {
QPixmap img(400, 400);
QPainter p(&img);
img.fill(QColor::fromRgb(0, 0, 0));
return img;
}

void InnerWidget::update_img_label(const QPixmap& img, QLabel* label) {
label->setPixmap(img);
label->setFixedSize(img.size());
}

void InnerWidget::init_img_label(QPixmap& img, QLabel*& label) {
img = create_black_img();
label = new QLabel{};
update_img_label(img, label);
}

void InnerWidget::zoom_img_label(double factor, QPixmap& img, QLabel* label) {
img = img.scaled(img.size() * factor);
update_img_label(img, label);
}

void InnerWidget::zoom(double factor) {
zoom_img_label(factor, m_img_1, m_label_1);
zoom_img_label(factor, m_img_2, m_label_2);
}

// --------------------------------------------------

MainWindow::MainWindow() :
m_edit_menu{ menuBar()->addMenu("&Edit") },
m_zoom_action{ new QAction{ "&Zoom" } },
m_central{ new QScrollArea },
m_main{ new QWidget } {

connect(m_zoom_action, &QAction::triggered, this, &MainWindow::zoom_inner);
m_edit_menu->addAction(m_zoom_action);

auto* layout = new QVBoxLayout{};
layout->setContentsMargins(0, 0, 0, 0);

for (int i = 0; i < 2; ++i) {
auto* entry = new InnerWidget;
m_entries.push_back(entry);
layout->addWidget(entry);
}

m_main->setLayout(layout);
m_central->setWidget(m_main);
setCentralWidget(m_central);

show();
update_max_width();
resize(maximumWidth(), 500);
}

void MainWindow::zoom_inner() {
bool confirm = false;
auto factor = QInputDialog::getDouble(this, "Zoom images", "Factor", 1.0, 0.1, 2.0, 2, &confirm, Qt::WindowFlags{}, 0.05);
if (!confirm) return;

for (auto* e : m_entries) {
e->zoom(factor);
e->adjustSize();
}

update_max_width();
resize(maximumWidth(), size().height());
}

void MainWindow::update_max_width() {
int max_entry_width = 0;
for (const auto* e : m_entries)
max_entry_width = std::max(max_entry_width, e->width());

setMaximumWidth(max_entry_width + m_central->verticalScrollBar()->width() + 3);
}



src/main.cc


#include <QtWidgets/QApplication>

#include <NestedWidgetResizeIssue_MWE.hh>

int main(int argc, char** argv) {
QApplication app(argc, argv);
MainWindow win;
return app.exec();
}



CMakeLists.txt


cmake_minimum_required(VERSION 3.17)
project(Test)

add_compile_options(-Wall -Wextra -pedantic)
set(CMAKE_CXX_STANDARD 17)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
cmake_policy(SET CMP0100 NEW)

find_package(Qt5 COMPONENTS Widgets REQUIRED)

include_directories(include)

add_executable(Test
src/main.cc
include/NestedWidgetResizeIssue_MWE.hh
src/NestedWidgetResizeIssue_MWE.cc
)

target_link_libraries(Test
Qt::Widgets
)

install(TARGETS
Test
DESTINATION "${CMAKE_SOURCE_DIR}/bin")



build.sh


#!/bin/bash

mkdir -p build bin

cd build
cmake ..
make
make install


Again to clarify
What I want to happen: all InnerWidget's should automatically resize (to a fixed size that is just enough to fully show the resized images) after confirming the zoom factor.
Issue: Everything except the InnerWidget's resizes correctly. Even calling InnerWidget::resize(.. directly has no effect, why?

EDIT

It seems I need to make the QScrollArea's widget resizable:



MainWindow::MainWindow()
// ...
{

// ...
m_central->setWidget(m_main);
m_central->setWidgetResizable(true); // I added this
setCentralWidget(m_central);
// ...
}


This works, but I don't understand why it is needed .. according to the documentation:

(...) Regardless of this property, you can programmatically resize the widget using widget()->resize(), and the scroll area will automatically adjust itself to the new size.
But calling InnerWidget::resize(.. did nothing. What am I missing?

d_stranz
25th December 2021, 16:39
But calling InnerWidget::resize(.. did nothing. What am I missing?

If you have widgets inside of a layout, the layout is in charge of the sizes. That's why there are size policies, size hints, QSpacerItem, stretch factors, and so forth, to help guide the layout as it resizes its content.

If your labels are growing too large with resizing, then adding a spacer at the bottom and/or top will squish them to their preferred sizes (QBoxLayout::addStretch()).