5

The main window of my Qt/C++ program looks like this:

main window

As you can see on the picture, the window consists essentially of:

  • a menu on the left
  • two "canvases"

What I want is straightforward to describe: I want that under resizing of the window, both canvases take as much space as possible, but still remain squares (width = height). I've been trying to achieve that unsuccessfully.

Let me quickly describe the objects:

  • The window is a class Window that I created deriving QWidget. It has a QGridLayout for a layout.
  • The window's layout has three widgets: the left menu LeftMenu *menu, and the canvases Canvas *leftCanvas, *rightCanvas. Both LeftMenu and Canvas are custom classes deriving QWidget.

(NB: the left menu actually consists of 3 different widgets (submenus), and the window also has a status bar and a top menu, but I don't think it matters for my question.)

I have been "playing" (not having fun the least bit) with QSizePolicy's etc to try to get the Canvases' sizes to behave like I want (be as large as possible inside the window, but keep height/width ratio = 1), unsuccessfully. Let me describe my latest attempt in case that is useful for you (if you already know a solution to my problem, you don't have to keep reading):

I overrode the methods heightForWidth(), sizeHint() and minimumSizeHint() for Canvas like so:

class Canvas : public QWidget
{
    Q_OBJECT

    friend class Window;

public:
    explicit Canvas(Window* window);
    ...

private:
    void resizeEvent(QResizeEvent *resizeEvent) override;
    int heightForWidth(int width) const override {return width;}
    QSize sizeHint() const override
    {
        int size = std::min(width(), height());
        return QSize(size, size);
    }
    QSize minimumSizeHint() const override {return QSize(200,200);}
    ...
};

And the constructor of my class Window looks like (a bit simplified):

Window::Window(ActionHandler *handler)
{
    leftMenu = new LeftMenu(this);
    leftMenu->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

    leftCanvas = new Canvas(this);
    rightCanvas = new Canvas(this);
    QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Maximum);
    policy.setHeightForWidth(true);
    leftCanvas->setSizePolicy(policy);
    rightCanvas->setSizePolicy(policy);    

    layout = new QGridLayout;    
    layout->setColumnMinimumWidth(0, menuWidth());    
    layout->addWidget(leftMenu, 0, 0);
    layout->addWidget(leftCanvas, 0, 1);
    layout->addWidget(rightCanvas, 0, 2);
    setLayout(layout);
}

My idea was that as long as the width of the canvases is the limiting factor, the sizePolicy of the canvases should be (QSizePolicy::Expanding, QSizePolicy::Maximum). And as soon as the height of the canvases becomes the limiting factor, I would change the sizePolicy of the canvases (probably in Canvas::resizeEvent()) to the opposite: (QSizePolicy::Maximum, QSizePolicy::Expanding). Does that sound too complicated?

Anyway, it already fails, and I don't understand why. If I shrink the window horizontally it gives me this:

enter image description here

So, the height of the canvases does not shrink. I do not understand this behavior. In the Qt documentation (http://doc.qt.io/qt-4.8/qsizepolicy.html#Policy-enum), I read:

QSizePolicy::Maximum The sizeHint() is a maximum. The widget can be shrunk any amount without detriment if other widgets need the space (e.g. a separator line). It cannot be larger than the size provided by sizeHint().

The behavior of my canvases here seems to contradict this: their height is larger than the height provided by their sizeHint(). (I did make sure by std::couting "live": the canvas sizeHint, its height, its sizePolicy and its hasHeightForWidth parameters).

Seub
  • 2,451
  • 4
  • 25
  • 34
  • Does [this example](http://stackoverflow.com/a/18923122/1329652) help you at all? If not, I'll have to come up with another one. Start with a small testcase and a mock widget to ensure that things work, and only then port it to your real app. – Kuba hasn't forgotten Monica Jul 01 '15 at 18:23
  • There are two approaches: one is to force a square widget, another is to simply draw a centered square sub-section of a rectangular widget. The first approach requires setting a size constraint on the root widget's layout, since the top-level window will have to grow horizontally to keep up with the horizontal space demands of the square widgets. At the minimum, you must then do `window->layout()->setSizeConstraint(QLayout::SetMinimumSize)`. – Kuba hasn't forgotten Monica Jul 01 '15 at 18:27
  • @KubaOber I still don't see how I can achieve the first approach (force a square widget). But the second approach is clever, I didn't even think about it. I'm tired of trying to understand how QLayout's and QSizePolicy's work, so I think I'm going to try to go for it. – Seub Jul 01 '15 at 18:45
  • The example I cite shows how to make a square widget. Does it not work for you? – Kuba hasn't forgotten Monica Jul 01 '15 at 19:19
  • @KubaOber: I don't know, it seems a bit hard to me to translate it into my setting. But on the other hand, I think your other solution (paint on a square region of a rectangular widget) is just perfect. It's simple, it's clean, it works great, and it saves the headaches of sizepolicies etc (by just using the Exapanding flags). I was able to make it work in my program in 10min, so I'm happy now. I think you should write a small answer suggesting to do that, and I'll accept it. – Seub Jul 01 '15 at 19:29
  • When the `sizeHint` or anything else that can affect a layout, changes, you need to call `QWidget::updateGeometry`. Do you? Also, your sizehint changing hack is most likely unnecessary since Qt does support square widgets, it's just a matter of making your widget properly implement the `heightForWidth` and `hasHeightForWidth`. It's all rather simple, but you must start with a simple layout and mock widgets, and only proceed to more complex cases in steps, verifying that things still work at every step. – Kuba hasn't forgotten Monica Jul 01 '15 at 19:39
  • I did try to call updateGeometry like you do in your example, it didn't change anything. I think what makes me decidedly not want to fight for the "first approach", requiring me to understand how sizePolicy and resizing works, is that I feel like Qt is "lying" to me when it says that a widget having a QSizePolicy::Maximum policy cannot be larger than its sizeHint. How am I supposed to understand anything in these conditions (; – Seub Jul 01 '15 at 19:49
  • The first approach would use `heightForWidth` and not sizeHint monkeying, though. Your approach is basically a third one that I didn't even mention, because I'd discourage it. It should also be noted that without setting a size constraint on the widget, the size policies may not be effective, since the window manager has the final say on how big the widget is, and the layout by default won't enlarge spacing between widgets IIRC. More details about that are e.g. in [this answer](http://stackoverflow.com/a/19011496/1329652) that demonstrates widget size policies that add spacing. – Kuba hasn't forgotten Monica Jul 01 '15 at 20:12
  • I think you're missing the fact that the layout simply can't fulfill your size hint coupled with the policy because the widgets on the left cannot be made any smaller, and they prevail. It also can't make the widgets any wider than the size of the window, since you gave it no authority (via `sizeConstraint`) to keep the window from being too small. I'd argue that you got exactly what you asked for, except you weren't aware of what you were really asking for :) – Kuba hasn't forgotten Monica Jul 01 '15 at 20:14
  • TL;DR: `sizePolicy` has effect only when all constraints, on all widgets, can be fulfilled at the same time. If other constraints, such as the size of the window or of the other widgets, cannot be fulfilled at the same time, the size policies become ignored according to some heuristic. It appears that the heuristic is "we can't make any widgets too small". Thus the controls on the left side win, and your widgets are too tall for their width. Once you make the window wide enough, the layout has no way of making it taller (no constraint!), and widgets will be too wide, per the same heuristic. – Kuba hasn't forgotten Monica Jul 01 '15 at 20:18
  • Thank you for your efforts to explain this to me, and trying to defend Qt''s layouts and resizing. Honestly it's still a nightmare for someone new to layout management like me. In spite of all your explanations, I still don't see why my canvases are being made too tall here. I don't think it's because the widgets on the left "win", because the canvases are actually not as tall as they are. They just have their initial height, and refuse to shrink. Let me also insist that I'm perfectly happy with your other solution, it's not just a "hack" in my opinion, it's just the best way to go. – Seub Jul 01 '15 at 20:34
  • I'll write an example for you when I have some time. The most important take away is that you need to start with just one widget in a layout, and make sure it behaves as you desire. Then start adding more widgets. If it doesn't work with just one widget inside a layout, then it won't work with more widgets either. – Kuba hasn't forgotten Monica Jul 01 '15 at 21:59
  • You're right of course. Please don't spend any more time than necessary, you already provided me with a solution I'm very happy with. – Seub Jul 02 '15 at 01:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82152/discussion-between-seub-and-kuba-ober). – Seub Jul 02 '15 at 03:05

0 Answers0