1 Attachment(s)
How to shrink a QMainWindow when its central widget is shrinked
Hi all,
Please take a look at these files:
List.h:
Code:
#ifndef LIST_H
#define LIST_H
#include <QMainWindow>
#include "task.h"
#include <QVector>
{
Q_OBJECT
public:
explicit List
(QWidget *parent
= nullptr
);
void updateStatus();
~List();
public slots:
void addTask();
void removeTask(Task*);
void taskStatusChanged();
private:
QVector<Task*> mTasks;
};
#endif // LIST_H
List.cpp:
Code:
#include "list.h"
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QString>
#include <QInputDialog>
#include <QLineEdit>
{
statusLabel
= new QLabel(tr
("Status: 0 todo / 0 done"));
hBox->addWidget(statusLabel);
hBox->addStretch();
hBox->addWidget(addTaskButtun);
vBox->addLayout(hBox);
vBox->addStretch();
widget->setLayout(vBox);
setCentralWidget(widget);
connect(addTaskButtun, &QPushButton::clicked, this, &List::addTask);
updateStatus();
}
//************************************************
void List::addTask() {
bool ok;
QLineEdit::Normal, tr
("Untitled task"),
&ok
);
if(ok && !name.isEmpty()) {
Task* task = new Task(name);
connect(task, &Task::removed, this, &List::removeTask);
connect(task, &Task::statusChanged, this, &List::taskStatusChanged);
mTasks.append(task);
vBox->addWidget(task);
updateStatus();
}
}
//*********************************************
void List::removeTask(Task* task) {
mTasks.removeOne(task);
vBox->removeWidget(task);
delete task;
updateStatus();
}
//**************************************
void List::taskStatusChanged() {
updateStatus();
}
//************************************
void List::updateStatus() {
int completedCount = 0;
for(auto t: mTasks)
if(t->isCompleted())
completedCount++;
int todoCount = mTasks.size() - completedCount;
statusLabel
->setText
(QString("Statuse: %1 todo / %2 completed") .arg(todoCount) .arg(completedCount));
}
//*****************************
List::~List()
{
delete vBox;
}
Task.h:
Code:
#ifndef TASK_H
#define TASK_H
#include <QWidget>
#include <QString>
{
Q_OBJECT
public:
explicit Task
(const QString
&,
QWidget* parent
= nullptr
);
void setName(const QString&);
bool isCompleted() const;
~Task();
public slots:
void rename();
signals:
void removed(Task*);
void statusChanged(Task*);
private slots:
void checked(bool);
private:
};
#endif // TASK_H
Task.cpp:
Code:
#include "task.h"
#include "list.h"
#include <QInputDialog>
#include <QWidget>
#include <QHBoxLayout>
#include <QPushButton>
#include <QCheckBox>
{
hBox->addWidget(checkbox);
hBox->addStretch();
hBox->addWidget(editButton);
hBox->addWidget(removeButton);
setLayout(hBox);
setName(name);
connect(editButton, &QPushButton::clicked, this, &Task::rename);
connect(removeButton, &QPushButton::clicked, [this]()->void {
emit removed(this);
});
connect(checkbox, &QCheckBox::toggled, this, &Task::checked);
};
//*************************************
void Task::setName(const QString& name) {
checkbox->setText(name);
}
//********************************************
return checkbox->text();
}
//*****************************
bool Task::isCompleted() const {
return checkbox->isChecked();
}
//****************************************
void Task::rename() {
bool ok;
if(ok && !value.isEmpty())
setName(value);
}
//************************************
void Task::checked(bool checked) {
QFont font
(checkbox
->font
());
font.setStrikeOut(checked);
checkbox->setFont(font);
emit statusChanged(this);
}
//**************************************************
Task::~Task() {
delete hBox;
}
When I add tasks "untitled task 1" to "untitled task 3", image number 1 below:
Attachment 13193
then remove "untitled task 2", the frame/window doesn't shrink and the space for that task is just vacant, image number 2. I like it to decrease and fit the remained tasks, like image number 3. When a task is added, the window increases in size, so when one is removed, it should decrease in size too.
I tested the followings one by one in void List::updateStatus() {, but none worked as expected!
What should be used instead, please?
Code:
this->sizeHint();
this->update();
this->resize(sizeHint());
this->adjustSize();
Re: How to shrink a QMainWindow when its central widget is shrinked
I think it is because in the List constructor, you add stretch to the vbox layout. The Task widgets you add go below that, so when you remove one, the stretch just expands to fill the empty space in between the hbox and the first remaining task.
Try not using the stretch at all. If that doesn't work, try using QBoxLayout::insertWidget() instead of addWidget() (which appends the new widget to the end of the layout, thus "trapping" the stretch between it and the start of the layout). In this case, your code would look something like this (assuming you still add the stretch during construction):
Code:
vbox->insertWidget( vbox->count() - 1, task );
I don't know if this will solve the non-shrink problem, but at least you won't have a gap when you remove a task because the stretch will always be at the end of the layout.
2 Attachment(s)
Re: How to shrink a QMainWindow when its central widget is shrinked
Neither worked unfortunately! :(
Without vBox->addStretch();, shrink won't be applied but merely we have a sparsely populated area, after removing some tasks:
Attachment 13194
And when I keep vBox->addStretch();, and use vBox->insertWidget(vBox->count() - 1, task); instead of vBox->addWidget(task);, we will have this, once again:
Attachment 13195
Re: How to shrink a QMainWindow when its central widget is shrinked
Yes, your second screen shot is what I expected to see.
What you will probably have to do is implement a resize() on your List window when you remove a Task. In that slot, get the size of the List window -before- removing the Task, get the size of the Task itself, remove it, then resize the List height to be the original height minus the Task height.
Re: How to shrink a QMainWindow when its central widget is shrinked
Good, thanks. There's always a way in programming! :)
I added the slot in List, but I think inside it, vBox should be resized, not? (And not the central widget of QMainWindow)
Re: How to shrink a QMainWindow when its central widget is shrinked
The QMainWindow is in charge of resizing its contents. I am pretty sure it asks the layout for the central widget for its size hint and adds the frame, menu bars, toolbars, status bars sizes as appropriate. So if the vbox layout never shrinks, it will return a size hint that includes the empty space.
But try it out to resize the vbox. If it works, it works. Riht now, I don't see any method in the QVBoxLayout class (or its base classes) that allows you to directly set the size.
Re: How to shrink a QMainWindow when its central widget is shrinked
Thanks.
I was actually much dealt with height() and width() in the List.cpp file, but these two both belong to the central widget and hence, I couldn't make the job done using them.
How to access the height/width of the base class (QMainWindow) which is indeed in charge of the overall size, please, so that we can modify it when a remove is accomplished?
Re: How to shrink a QMainWindow when its central widget is shrinked
Your List class is derived from QMainWindow, isn't it? If your slots to add / remove new Task widgets are in the List class, then you can just call "height()" in those slots and you will get the height of the List widget. You then just call resize() after you compute the new height.
You don't want to change the height using the pointer to the central widget because QMainWindow (List) and the layout won't let it happen.
Re: How to shrink a QMainWindow when its central widget is shrinked
Quote:
Your List class is derived from QMainWindow, isn't it? If your slots to add / remove new Task widgets are in the List class, then you can just call "height()" in those slots and you will get the height of the List widget. You then just call resize() after you compute the new height.
I added resize(height() - 100, width()) to removeTask, (-100, just to test):
Code:
void List::removeTask(Task* task) {
mTasks.removeOne(task);
vBox->removeWidget(task);
delete task;
updateStatus();
resize(height() - 100, width());
}
When I hover the mouse on height(), this message is shown:
inline int QWidget::height() const , so that QWidget in our example is List, right?
So in that slot, when we use resize via a smaller height, the widget List must resize normally, and since this slot is called each time we remove a Task, therefore List is expected to shrink that way.
But it doesn't work as expected!
Re: How to shrink a QMainWindow when its central widget is shrinked
Quote:
But it doesn't work as expected!
Well, what does happen? Anything? Nothing?
You should probably call "task->deleteLater()" instead of "delete task". You are in the middle of a slot, and control needs to return to the event loop so that Qt can clean up properly and take whatever other action is needed to redraw the UI. deleteLater() will delete the widget when the event loop runs.
2 Attachment(s)
Re: How to shrink a QMainWindow when its central widget is shrinked
Very good, thanks for the precious info. I use the function this way:
Code:
void List::removeTask(Task* task) {
mTasks.removeOne(task);
vBox->removeWidget(task);
task->deleteLater();
updateStatus();
resize(height() - 100, width());
}
After entering 5 tasks:
Attachment 13198
And then deleting the first four ones, I got this! It's actually the way I meant I doesn't work. :(
Attachment 13199
Re: How to shrink a QMainWindow when its central widget is shrinked
Your last screen shot looks like you still have the spacer item as the first thing in the vbox. If the spacer item was at the end, task 5 would be pushed to the top of the layout. Instead, it looks like the spacer item is first and is pushing everything down.
Please show your code for adding a new task to the list.
Re: How to shrink a QMainWindow when its central widget is shrinked
Quote:
Your last screen shot looks like you still have the spacer item as the first thing in the vbox.
Yes. We firstly have a button and label of the list on the top, and to hold it on top we need the spacer which comes after these. Afterwards, in the addTask slot, we have this code, as before:
Code:
void List::addTask() {
bool ok;
QLineEdit::Normal, tr
("Untitled task"),
&ok
);
if(ok && !name.isEmpty()) {
Task* task = new Task(name);
connect(task, &Task::removed, this, &List::removeTask);
connect(task, &Task::statusChanged, this, &List::taskStatusChanged);
mTasks.append(task);
vBox->addWidget(task);
updateStatus();
}
}
Re: How to shrink a QMainWindow when its central widget is shrinked
Quote:
We firstly have a button and label of the list on the top, and to hold it on top we need the spacer which comes after these.
Well, that's the problem. Your code calls addWidget() instead of insertWidget() (see my post from earlier in this thread), which means that the spacer stays at the top of the vbox and pushes both up and down. The button and label get pushed up and the tasks get pushed down. When you add new tasks, this results in the window expanding. When you remove a task, the spacer just expands to add more space between the top and the remaining tasks and the window doesn't shrink.
When you add a task, you need to insert it -above- the spacer so that the spacer is always the last thing in the vbox. When you remove a task, you need to resize the window so the spacer will shrink.
1 Attachment(s)
Re: How to shrink a QMainWindow when its central widget is shrinked
Yes, thank you. These are right in theory, but when tested in practice, the result is quite different!
I did these:
Firstly removed vBox->addStretch(); in List's constructor. Then changed void List::addTask() to this:
Code:
void List::addTask() {
bool ok;
QLineEdit::Normal, tr
("Untitled task"),
&ok
);
if(ok && !name.isEmpty()) {
Task* task = new Task(name);
connect(task, &Task::removed, this, &List::removeTask);
connect(task, &Task::statusChanged, this, &List::taskStatusChanged);
mTasks.append(task);
vBox->insertWidget(vBox->count()-1, task);
vBox->addStretch();
updateStatus();
}
}
And here's also:
Code:
void List::removeTask(Task* task) {
mTasks.removeOne(task);
vBox->removeWidget(task);
task->deleteLater();
updateStatus();
resize(height() - 100, width());
}
The result is:
Attachment 13204
Why don't you test the project once?
Re: How to shrink a QMainWindow when its central widget is shrinked
Quote:
Why don't you test the project once?
Because I have my own code to write...
In your addTask() slot, lines 11 and 12, you are adding a new stretch item every time you add a new task, but in the removeTask() slot, you are only removing the task widget. So you end up with a vbox that contains one stretch item for every task you add, and they never go away. So when you call insertWidget(), you are probably putting the new task in between two stretch items and then sticking a new stretch item onto the end of that.
Going back to your original post in this thread, you are building the central widget by:
1 - creating a generic QWidget to act as the central widget.
2 - adding a vbox as its layout.
3 - inserting an hbox layout with your buttons, etc. as the first item in the vbox
4 - adding a stretch to the end of the vbox to push the hbox to the top of the vertical layout
OK so far. Your desired behavior is for the main window to grow as you add Task items (widgets) to it and to shrink as they are removed.
To add a new task, you want to insert it between the stretch item (which is always at the bottom of the vbox) and whatever is above it (the hbox, more tasks, whatever).
After the initial construction of the vbox, the item count is 2 (the hbox and the stretch). So you want to call insertWidget() with the argument count - 1. You do not add new stretch and you do not change the position of the stretch that you added at the start. It always stays at the bottom of the vbox and everything else goes above it and below the hbox.
To add more tasks to the list, you do exactly the same - insertWidget with count - 1 as the position argument.
To remove a task, your existing code should be OK. Find the task widget in the vbox, take it, then resize the vbox to remove the space taken up by the widget you just removed. The stretch still stays at the bottom and the hbox at the top, unchanged.
2 Attachment(s)
Re: How to shrink a QMainWindow when its central widget is shrinked
Only these parts are changed compared to the first code:
Code:
{
statusLabel
= new QLabel(tr
("Status: 0 todo / 0 done"));
hBox->addWidget(statusLabel);
hBox->addStretch();
hBox->addWidget(addTaskButtun);
vBox->addLayout(hBox);
vBox->addStretch();
cWidget->setLayout(vBox);
setCentralWidget(cWidget);
connect(addTaskButtun, &QPushButton::clicked, this, &List::addTask);
updateStatus();
}
//************************************************
void List::addTask() {
bool ok;
QLineEdit::Normal, tr
("Untitled task"),
&ok
);
if(ok && !name.isEmpty()) {
Task* task = new Task(name);
connect(task, &Task::removed, this, &List::removeTask);
connect(task, &Task::statusChanged, this, &List::taskStatusChanged);
mTasks.append(task);
vBox->insertWidget(vBox->count()-1, task);
updateStatus();
}
}
//*********************************************
void List::removeTask(Task* task) {
mTasks.removeOne(task);
vBox->removeWidget(task);
task->deleteLater();
updateStatus();
resize(height() - task->height(), task->width()); // Here we resize the window, List
}
The outcome is not very bad but still far from a decent app!
Attachment 13208
Attachment 13209
Re: How to shrink a QMainWindow when its central widget is shrinked
OK, here's one last thing to try. The stretch at the bottom really seems to be messing things up when trying to dynamically resize the main window.
In removeTask(), try this:
- remove the task.
- remove the stretch
- resize the height
- add the stretch back in
If this still doesn't work, then I will implement some code and solve it. It can't be this hard.
1 Attachment(s)
Re: How to shrink a QMainWindow when its central widget is shrinked
A question, in vBox->insertWidget(vBox->count()-1, task);
Isn't "count" one beyond the last element? And "count-1" the last indeed element? Also, isn't "stretch" counted as an element in the layout?
Anyway, I used this for the function:
Code:
void List::removeTask(Task* task) {
mTasks.removeOne(task);
vBox->removeWidget(task);
task->deleteLater();
delete vBox->takeAt(vBox->count()-1);
resize(height() - task->height(), task->width()); // Here we resize the window, List
vBox->addStretch();
updateStatus();
}
And after adding a number of task and removing them continually reaching the fist task, the following result emerged:
Attachment 13214
Re: How to shrink a QMainWindow when its central widget is shrinked
The "index" argument in insertWidget() is the place where the new widget will be placed. So if the vbox has two items in it (the hbox and the stretch), count = 2, count - 1 = 1, so the widget will be inserted at index 1, which is between the hbox and the stretch. The stretch becomes index 3. The hbox stays at index 0.
So in order for insertWidget( vbox->count() - 1 ) to work correctly, there has to be a minimum of two items in the vbox, so you have to build it with the hbox and the stretch in place before you start adding tasks. If you don't have the stretch, then the first task will be added at 0, on top of the hbox. Subsequent tasks will be added on top of the hbox as well.
If I can find time, I will work on this sometime before the end of the week.