A Qt app with auto-resizing docks

5 minute read

Most desktop apps have a main window with one child window which displays the document surrounded by toolbars, toolboxes and palettes. You know what I’m talking about.

Here’s an image from the Qt documentation.

Qt app main window layout

Ostinato (a desktop app built using Qt), however, has a main window with 3 dock windows (QDockWidget) - Ports & Streams, Port Statistics and Logs, but no central widget.

Ostinato main window at start

When Ostinato starts, two child windows are programmatically set to equally share the vertical space. The third Logs window is added as a tab to the Port Statistics window, since it is informational and need not occupy screen estate, until required.

Now as long as you don’t change the size of the main window all is good. But if you increase (or maximize) the window, you end up with something like this -

Ostinato main window maximized

Ugh!

All that wasted space and ugly looking to boot!

I immediately drag the two windows so that they take up the full space again, before continuing to work.

But some customers don’t. Or worse - don’t realize that they can resize the windows manually!

This wasn’t the highest priority item to address all these years and so I didn’t pay much attention to it other than cursory look for some solution, which I didn’t find.

Solution to what you ask?

To utilize all the space available for the Ostinato dock windows whenever the main window is resized.

Last week, I decided to take a look (read: I was tired after some serious debugging of a customer issue and needed something lighter to tackle!).

First try

My first thought was to hook into main window resizes using QMainWindow::resizeEvent() and programmatically resize the dock windows using QMainWindow::resizeDocks() to take up half the main window each as done at start up.

Which worked.

But, I immediately realized that before the user changes the main window size, they might have changed the layout or sizes of the dock windows. In such a situation, any change to the main window size should retain the user layout and only change the dock window sizes proportionally.

Second try

My second attempt was to try and calculate the new sizes of the dock windows as follows -

dockNewSize = (newMainWindowSize/oldMainWindowSize)*dockOldSize

Which is all fine and dandy. Within QMainWindow::resizeEvent() the input parameter gives us both the old and new size of the main window, but the dock windows have already been resized by Qt’s own logic and we don’t have access to the old dock sizes!

The only way to get access is to maintain the dock sizes across all dock and main windows resizes. That’s too much trouble for a lazy programmer like me!

That’s when I thought of a different way to approach the problem.

Third try

Don’t change the dock sizes at all. Instead, change the central widget’s size.

Of course, we didn’t have a central widget currently, so I created dummyCentral (a QWidget) and called QMainWindow::setCentralWidget(dummyCentral). Then, in resizeEvent(), call dummyCentral->resize(0, 0). Setting the central widget to a size 0 would make the dock windows automatically resize utilizing all the available space while retaining the current dock layout!

Pure Genius!

Except that it didn’t work.

(It should have, but it didn’t. Continue reading to know why!)

It was late and I decided to call it a day feeling mentally exhausted.

But as so often happens when you go to bed with a problem on your mind, you don’t sleep well and you are tossing and turning with thoughts of the problem still in your head.

The central widget resizing strategy should work - there’s no reason why it shouldn’t!

So I decided to take another look the next day.

Fourth time is the charm

While looking at the code for the (dummy) central widget that I had written, I realized I didn’t need to resize the dummy widget, if I set it to (0, 0) before I originally add it to the main window!

Unless, Qt handles (0, 0) size specially and doesn’t add it to the main window at all (No, it doesn’t handle it specially)!

Also, it helps if you can see something on the screen while debugging!

So, I removed the resizeEvent() method and created a QLabel as the central widget and set its text to XANADU.

Don’t ask me why - it was the first word that came to mind; something to do with Mandrake, the magician I presume!

When I ran the code, no XANADU on the screen!

Head-scratcher! But something to dig further!

Looking at the dummy widget creation code and the code around it, I noticed that I was calling setCentralWiget() before setupUi() (the main window uses a Qt .ui file).

That didn’t seem right, so I moved the call to setCentralWidget() after setupUi() and addDockWidget() calls.

Voila!

XANADU magically appears inside the main window!

Maximizing the main window resizes the docks to utilize all the space around XANADU!

Here’s my final cleaned-up code that I committed to the repo.

    QWidget *cw = new QWidget(this);
    cw->setFixedSize(0, 10); // fixed => both min and max size
    cw->setStyleSheet("background: red");

    ...

    // Set central widget after adding docks
    setCentralWidget(cw);

And here’s the result of adding those 4 lines of code - docks use all the space available!

Docks utilizing all the space

The careful reader would have noted and may be wondering, why I set the dummy widget height to 10 instead of 0?

With a size of (0, 0) for the central widget, the left/right dock areas don’t seem to be available, so using a width of 0 ensures that the widget is not visible and a height of 10px ensures that there is some space between top and bottom docks that user can insert a dock in the left/right areas (to create a middle row, so to speak).

Even with the 10px, I find that it’s still quite fiddly dragging a dock to the left/right dock areas for the main window to recognize and visually indicate its readiness to accept the drop there! Increasing the height of the main window seems to help.

The red background is not really needed, but I left it in for debug purposes - I just need to change the width to a non-zero value and the red widget should show up and stand out on screen.

Here’s a screenshot with three ducks docks in a row column -

Three docks in a column

Ok, that pun was a stretch, I know!

But talking of stretches, the three row dock windows are not behaving like I’d like them to with respect to vertical stretch (logs window seems to hog the space, while I would like the stats window to get the space). The usual QWidget::sizePolicy::verticalStretch doesn’t work because these three windows are not in a normal QLayout, but in three separate QDockWidget laid out by the QMainWindow’s dock-aware layout manager.

I probably need to play around with sizeHint(), sizePolicy() or the minimum(), maximum() size constraints for one or more of those dock windows to get the vertical space utilization I’d like - but that’s a bunch of experiments for another day (and another blog post)!

For more Ostinato related content, subscribe for email updates.

Leave a Comment