Preserving/Restoring QDockWidget size
-
@Perdrix
Saving the geometry of a dock widget hasn't been implemented.
You would have to serialize that manually.A small nitpick: When reading settings, I'd prefer to instantiate
const QSettings settings
, just to make sure I don't write anything by accident. Flushing withsync()
otherwise happens automagically when the object goes out of scope. -
@Axel-Spoerl Oh! No wonder I was having trouble getting that to work. I thought that's what restoreDockWidget() did given the windowState of the main window!
If I need to save the geometry of the dock widget (whether docked or not) how should I do that?
I just checked the documentation again and it clearly says:
QByteArray QWidget::saveGeometry() const
Saves the current geometry and state for top-level widgets.
To save the geometry when the window closes, you can implement a close event like this:
void MyWidget::closeEvent(QCloseEvent *event)
{
QSettings settings("MyCompany", "MyApp");
settings.setValue("geometry", saveGeometry());
QWidget::closeEvent(event);
}See the Window Geometry documentation for an overview of geometry issues with windows.
Use QMainWindow::saveState() to save the geometry and the state of toolbars and dock widgets. -
@Perdrix
restoreDockWidget()
is a mixed bag IMHO. It basically restores what the main window needs to know about its dock widgets. Their geometry doesn't fall into that category.The code snippet you mention is meant to be included in a class inheriting from
QWidget
. As far as I know your app, you are using generic dock widgets.So the question is, is the dock widgets' geometry the right one at the moment, where you call
QMainWindow::saveState()
? If so, just loop over the dock widgets and do as laid out in the code snippet. If not (because they might have been closed manually at an earlier time), you would have to install an event filter on each dock widget and intercept the event that hides, closes or resizes a dock widget. If you don't wanna write into the settings file from an event, you could store the returnedQByteArray
in aQHash
and use that to restore geometries at run time. When saving settings, you could translate the hashed pointer into an object name, store the hash in the settings and rebuild it on next application startup. -
@Axel-Spoerl But that's my complaint - it doesn't restore the size of the docked dock widget to what it was when the application closed.
So I have to ask again how can this be made to work? I'm prepared to add extra code enquire on the geometry of the dock widget and restore it after open, but only if that has some chance to work.
-
@Axel-Spoerl I tried this:
// // Now save the geometry of the Picture List dock widget // settings.beginGroup("PictureList"); geometry = pictureList->saveGeometry(); #ifndef NDEBUG ZTRACE_RUNTIME("Hex dump of Picture List geometry:"); ZTrace::dumpHex(geometry.constData(), geometry.length()); #endif settings.endGroup();
if (windowState.length()) { restoreState(windowState); restoreDockWidget(pictureList); settings.beginGroup("PictureList"); geometry = settings.value("geometry", QByteArray()).toByteArray(); settings.endGroup(); if (geometry.length()) { pictureList->restoreGeometry(geometry); } }
But even though I saved the dock widget's geometry explicitly restoring it didn't change the size to match what it was when the application closed.
:(
David -
@Perdrix
Hi David,
I shall have a look and revert back to you asap.
cheers
Axel -
@Axel-Spoerl Thank you Axel.
FYI I also tried:
// // Now save the height of the Picture List dock widget // settings.beginGroup("PictureList"); auto height = pictureList->height(); settings.setValue("Height", height); #ifndef NDEBUG ZTRACE_RUNTIME("Height of Picture List was %d pixels", height); #endif settings.endGroup();
and
if (windowState.length()) { restoreState(windowState); restoreDockWidget(pictureList); settings.beginGroup("PictureList"); auto height = settings.value("Height", 0).toInt(); settings.endGroup(); if (height) { resizeDocks({ pictureList }, { height }, Qt::Vertical); } }
which also didn't work :(
-
@Axel-Spoerl Odd
This code looks as if it is saving the sizes of the dock widgets ...
void QDockAreaLayout::saveState(QDataStream &stream) const { stream << (uchar) DockWidgetStateMarker; int cnt = 0; for (int i = 0; i < QInternal::DockCount; ++i) { if (!docks[i].item_list.isEmpty()) ++cnt; } stream << cnt; for (int i = 0; i < QInternal::DockCount; ++i) { if (docks[i].item_list.isEmpty()) continue; stream << i << docks[i].rect.size(); docks[i].saveState(stream); } stream << centralWidgetRect.size(); for (int i = 0; i < 4; ++i) stream << static_cast<int>(corners[i]); }
-
@Perdrix
Hi David,
making sure we're on the same page.
The workflow is:- application starts with a docked dock widget.
- dock widget is unplugged programmatically by
setFloating(true)
. - dock widget is resized, either manually or programmatically.
- application is closed, state and geometry are saved.
- application is started again, state and geometry are restored.
- problem: dock widget bounces back to default geometry.
Cheers
Axel -
@Axel-Spoerl
Ok, I must apologize, you have found a bug, a missing feature, whatever you wanna call it.If I understand you correctly, the issue is the undocked geometry.
It is held byQDockWidgetPrivate::undockedGeometry
and unfortunately not saved anywhere.
I'll look into a fix. It is, however, not an easy patch.QWidget::saveGeometry()
is not virtual, so the straight forward approach of overriding it inQDockWidget
won't fly. The undocked geometry is held in the private header, without public accessors, so a stright forward workaround won't fly either.- The fix will probably be a version bump of
QDockAreaLayout::saveState()
. But that'll kick be available in the next release of 6.5 at the earliest. - The workaround is a bit of plumbing. When the application closes, all dock widgets need to be made floating, after the main window state was saved. Then their undocked geometry becomes available in
geometry()
and needs to be saved as well. I'll be back with a suggestion.
- The fix will probably be a version bump of
-
@Axel-Spoerl Nope this is the DOCKED size.
So work flow is:
- Open application
- Adjust vertical height of lower dock widget
- Close application
- Reopen application. Size lower dock widget is same as if lower dock widget hadn't been resized.
- Open application
-
@Perdrix
Hi David,so we're not talking about undocked dock widgets' geometry?
In that case, you're not affected by the bug, that the undocked geometry is not saved.I have the feeling that something is wrong in the application code.
Just tried to demonstrate the problem in a simple reproducer. But there is no problem, it works perfectly
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QDockWidget> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: void readSettings(); void writeSettings() const; Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "./ui_mainwindow.h" #include <QSettings> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); for (int i = 0; i < 4; ++i) { const QString name = "DockWidget-" + QString::number(i); QDockWidget *dw = new QDockWidget(name, this); dw->setObjectName(name); addDockWidget(static_cast<Qt::DockWidgetArea>(i << 2), dw); } readSettings(); } MainWindow::~MainWindow() { writeSettings(); delete ui; } void MainWindow::readSettings() { const QSettings s("Test", "DockWidgetGeometry"); if (!s.contains("MainWindowState")) return; const QByteArray array = s.value("MainWindowState").toByteArray(); restoreState(array); } void MainWindow::writeSettings() const { QSettings s("Test", "DockWidgetGeometry"); s.setValue("MainWindowState", saveState()); }
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
I start the application, move all dock widgets to the left dock, join two as floating tabs, change their sizes. Whatever I do is exactly recovered after closing and re-starting the app.
So I suspect that your application touches the docked dockwidgets' size after the state is restored.
Cheers
Axel -
@Axel-Spoerl The only code I have the messes with its size is in SetupUi, and that is done before the code that tries to restore its size.
void setupUi(QDockWidget *PictureList) { if (PictureList->objectName().isEmpty()) PictureList->setObjectName("PictureList"); PictureList->resize(618, 156); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(PictureList->sizePolicy().hasHeightForWidth()); PictureList->setSizePolicy(sizePolicy); PictureList->setMinimumSize(QSize(618, 156)); PictureList->setStyleSheet(QString::fromUtf8("")); PictureList->setFeatures(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable); PictureList->setAllowedAreas(Qt::BottomDockWidgetArea|Qt::RightDockWidgetArea); PictureList->setWindowTitle(QString::fromUtf8("Light Frames: 0 - Dark Frames: 0 - Flat Frames: 0 - Dark Flat Frames: 0 - Offset/Bias Frames: 0")); dockWidgetContents = new QWidget(); dockWidgetContents->setObjectName("dockWidgetContents"); QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Expanding); sizePolicy1.setHorizontalStretch(0); sizePolicy1.setVerticalStretch(0); sizePolicy1.setHeightForWidth(dockWidgetContents->sizePolicy().hasHeightForWidth()); dockWidgetContents->setSizePolicy(sizePolicy1); verticalLayout = new QVBoxLayout(dockWidgetContents); verticalLayout->setObjectName("verticalLayout"); tableView = new QTableView(dockWidgetContents); tableView->setObjectName("tableView"); verticalLayout->addWidget(tableView); tabBar = new QTabBar(dockWidgetContents); tabBar->setObjectName("tabBar"); QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Minimum); sizePolicy2.setHorizontalStretch(0); sizePolicy2.setVerticalStretch(0); sizePolicy2.setHeightForWidth(tabBar->sizePolicy().hasHeightForWidth()); tabBar->setSizePolicy(sizePolicy2); tabBar->setMinimumSize(QSize(0, 28)); tabBar->setMaximumSize(QSize(16777215, 28)); verticalLayout->addWidget(tabBar); PictureList->setWidget(dockWidgetContents); retranslateUi(PictureList); QMetaObject::connectSlotsByName(PictureList); } // setupUi
What's more is that if the QSettings values for geometry and windowState aren't present, the only code that I have to adjust the size is at the start of the code I posted earlier where it does
resizeDocks({ pictureList }, { 150 }, Qt::Vertical);
and that appears to set the size just fine for the FIRST time open:
Subsequent opens always open to a large size that appears related to the main window size.
-
@Perdrix Maybe I am trying to restore the geometry and window state at the wrong time? I am doing it in code invoked from showEvent()
void DeepSkyStacker::showEvent(QShowEvent* event) { if (!event->spontaneous()) { if (!initialised) { initialised = true; onInitialise(); } } // Invoke base class showEvent() return Inherited::showEvent(event); }
-
@Perdrix
Hi David,
If I tweak the reproducer and overrideQMainWindow::showEvent(QShowEvent *event)
, it works as well.I don't know about the
DeepSkyStacker
class, where it inherits from and when its first non-spontaneuous show event is consumed. My gut feeling tells me to put some pocket money on the show event. It's an unusual place to callQMainWindow::restoreState()
: Are the dock widgets are already constructed and added to the main window, when the show event is consumed? The dock widget part of the call will be a no-op, if the dock widgets aren't in place or don't have an object name.I'd happily look into the code of DSS, because it's a great piece of software (have I ever mentioned that? Apologies if I haven't). It's a VS project, however, and I only use Qt Creator.
I'd try the following:
- play with the reproducer in my earlier post, to see if the issue can be isolated
- play with the dock widgets: Tab them, undock them, close and restart the app => see if anything is restored at all.
- move the the code calling
QMainWindow::restoreState()
right after the construction of the dock widgets.
Cheers
Axel -
@Axel-Spoerl The widgets are also constructed in onInitialise().
You suggest that showEvent() is an unusual place from which to call restoreState(). Where would you normally put it? At the end of the ctor as you did in you small example?
-
@Perdrix said in Preserving/Restoring QDockWidget size:
At the end of the ctor as you did in you small example?
The c'tor is definitely a good spot, but it has to be called after the dock widgets are fully created.
-
@Axel-Spoerl Moved the code to the ctor and all now works!
The final code now reads:
ZTRACE_RUNTIME("Restoring Window State and Position"); QSettings settings{}; settings.beginGroup("MainWindow"); if (settings.contains("geometry") && settings.contains("maximised")) { const QByteArray geometry{ settings.value("geometry").toByteArray() }; const bool maximised{ settings.value("maximised").toBool() }; if (maximised) { showMaximized(); setGeometry(screen()->availableGeometry()); } else { restoreGeometry(geometry); } } if (settings.contains("windowState")) { auto windowState{ settings.value("windowState").toByteArray() }; restoreState(windowState); } settings.endGroup();
Thanks
David -
@Perdrix
Hey David! Great! Glad that it works!
Just two minor nitpicks:- no need to instantiate settings with curly braces.
- if the settings object is meant to be only for reading settings, I would constify it. I've had strange bugs where settings became automagically written. E.g. in your case, an empty "MainWindow" group would be created. Such things are hard to debug, hence my constification paranoia.