dynamic docks and saveState
-
I have two types of dock windows in my app: permanent docks which are always created on startup and can only have their visibility toggled by the user, and dynamic docks which can be created and destroyed as needed. When I destroy a dynamic dock, I essentially do the following:
removeDockWidget(dock); dock->setParent(nullptr); delete dock;
I assign unique names to all docks so their state and geometry can be saved/restored by the app. All this works fine with one or two quirks. One quirk is that the size o f the byte array saved by the app with
saveState
is not stable and grows continuously if I delete a dynamic dock and then create a new one to replace it. The new one is guaranteed to have a different object name. It is as if there is some mystery data still existent from the deleted dock. Is there anything else I need to clean up so thatsaveState
only saves the current docks when the app exits? -
@Phil-K said in dynamic docks and saveState:
size o f the byte array saved by the app with saveState is not stable and grows continuously
What
byteArray
? The setting are stored on the stack, so they are only temporarily available until the window has been restored.Are you talking about
byteArray
as described in this example?
https://doc.qt.io/qt-5/qmainwindow.html#restoreState -
The byte array saved in the QSettings config file when the app closes, as in:
QSettings settings; settings.setValue("windowState", saveState());
The data on that key gets larger and larger when creating and deleting my dynamic docks( inspecting the key after the app exits), even though I thought I was removing the dynamic dock widgets properly, i.e. my concern is the size of the config file itself. I don't want it to expand indefinitely like that.
-
Hi,
That's an intriguing issue.
Can you provide a minimal compilable example that shows that behaviour ?
Which version of Qt are you using ?
-
@SGaist OK Here's a minimal example below. I am using Qt 5.14.2 on both Linux and Windows.
Create a c++ widget app with no ui and use the files below. Start the app and use the menu to create a few docks. Close the app with some docks in the window. Inspect the
dyndock.ini
and note the contents and size. Restart the app. The docks are recreated as expected. Use the menu to delete all docks, then create some new ones. Exit the app and inspect the .ini again. Repeat this cycle a few times and note thestate
key size in the .ini file. You should see that key growing larger and larger.// main.cpp #include "mainwindow.h" #include <QApplication> #include <QSettings> int main(int argc, char *argv[]) { QApplication::setApplicationName("dyndock"); QApplication::setOrganizationName("test"); QSettings::setDefaultFormat(QSettings::IniFormat); QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
// mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class QAction; class QMenu; class QUuid; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow() override; private: void CreateMenu(); void RestoreWindows(); void SaveSettings(); private slots: void OnCreateDocks(); void OnDeleteDocks(); private: // Qt overrides void closeEvent(QCloseEvent *event) override; private: QAction *file_create_docks_; QAction *file_delete_docks_; QMenu *file_menu_; }; #endif // MAINWINDOW_H
// mainwindow.cpp #include "mainwindow.h" #include <QAction> #include <QDockWidget> #include <QMenuBar> #include <QSettings> #include <QUuid> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), file_create_docks_(nullptr), file_delete_docks_(nullptr), file_menu_(nullptr) { CreateMenu(); QWidget *central_widget = new QWidget(this); central_widget->setMinimumSize(400, 400); setCentralWidget(central_widget); RestoreWindows(); } MainWindow::~MainWindow() { } void MainWindow::CreateMenu() { file_create_docks_ = new QAction("Create Dock", this); file_delete_docks_ = new QAction("Delete All Docks", this); connect(file_create_docks_, &QAction::triggered, this, &MainWindow::OnCreateDocks); connect(file_delete_docks_, &QAction::triggered, this, &MainWindow::OnDeleteDocks); file_menu_ = menuBar()->addMenu("&File"); file_menu_->addAction(file_create_docks_); file_menu_->addAction(file_delete_docks_); } void MainWindow::RestoreWindows() { QSettings settings; settings.beginGroup("Docks"); const int size = settings.beginReadArray("dock"); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); const QUuid uuid = settings.value("uuid").toUuid(); const QString title = uuid.toString(QUuid::WithoutBraces); QDockWidget *dock = new QDockWidget(title, this); QWidget *dock_widget = new QWidget; dock->setObjectName(title); dock->setWidget(dock_widget); dock->setFeatures(QDockWidget::DockWidgetMovable); dock_widget->setMinimumSize(400, 100); addDockWidget(Qt::RightDockWidgetArea, dock); dock_widget->setFocus(); } settings.endArray(); settings.endGroup(); settings.beginGroup("Windows"); restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("state").toByteArray()); settings.endGroup(); } void MainWindow::SaveSettings() { QSettings settings; settings.beginGroup("Docks"); settings.remove("dock"); settings.beginWriteArray("dock"); QList<QDockWidget *> docks = findChildren<QDockWidget *>(); int i = 0; for (auto it = docks.begin(); it != docks.end(); ++it) { QDockWidget *dock = *it; settings.setArrayIndex(i); settings.setValue("uuid", dock->objectName()); ++i; } settings.endArray(); settings.endGroup(); settings.beginGroup("Windows"); settings.setValue("geometry", saveGeometry()); settings.setValue("state", saveState()); settings.endGroup(); } void MainWindow::OnCreateDocks() { // create/add a dock const QUuid uuid = QUuid::createUuid(); const QString title = uuid.toString(QUuid::WithoutBraces); QDockWidget *dock = new QDockWidget(title, this); QWidget *dock_widget = new QWidget; dock->setObjectName(title); dock->setWidget(dock_widget); dock->setFeatures(QDockWidget::DockWidgetMovable); dock_widget->setMinimumSize(400, 100); addDockWidget(Qt::RightDockWidgetArea, dock); } void MainWindow::OnDeleteDocks() { // remove/destroy all docks QList<QDockWidget *> docks = findChildren<QDockWidget *>(); for (auto it = docks.begin(); it != docks.end(); ++it) { QDockWidget *dock = *it; removeDockWidget(dock); dock->setParent(nullptr); delete dock; dock = nullptr; } } void MainWindow::closeEvent(QCloseEvent *event) { Q_UNUSED(event) SaveSettings(); }
-
I've encountered this as well some time ago. I didn't go very deep into the QMainWindow code, but it seems it basically never discards information about the removed docks. My guess is this is supposed to be a feature not a bug. If you delete a dock and later recreate it with the same name you can restore it the way it was before.
This of course works for a constant set of docks that don't change, but yeah, also leads to the problem you described if you have many unique names. In my case this led to extreme settings file bloat and after couple weeks of using the app users experienced worsening slowdowns on settings load/save. There were thousands of entries in the settings file even though only a few actual docks.
The way we dealt with it was to create a pool of names/ids that would only grow when there were more docks than slots in the pool (named e.g. "dock_0" to "dock_xx"). We would then keep track of the free and occupied slots in the pool and reuse them as they come and go. Later on we added a grouping feature that would kinda tag certain docks to let them reuse ids only from the same group as theirs. This had a nice additional property of similar docks appearing in the same spot in the app instead of always recreated in some default place (which previously annoyed a lot of people). All in all this let us keep number of entries in the settings at pretty much constant maximum level limited to a fixed number of docks we allowed to be shown at the same time.
From implementation point of view we've inherited from QDockWidget and in its constructor it picked an id from the free pool and then in destructor returned it to that pool. We also kept track which docks were opened at the time of saving state so that at the time of loading they would be given the same ids they had in previous session and thus were restored in the same place.
-
@Chris-Kawa OK, based on what you've described, I'm going to alter my design so that it uses a fixed size pool of available docks instead of using dynamic object naming with uuids. Not gonna fight this one since I have so many other battles to wage.
Might be worth amending the Qt docs to clarify the hazard, i.e.
removeDockWidget
does not remove all window state related to that dock.Thanks for your input.
-
You should check the bug report system to see if there's something related. If not please open a ticket to suggest that edition. Provide your example as well so if it's considered a bug the debugging can start faster.