Thursday, August 21, 2014

Frames and Layouts

Today I spent about 4 hours futzing with the coding of the UI for findview. That is, creating the buttons and fields that make up the UI, and arranging them in layout objects. This was a tedious business for two reasons. One reason is that for every little tweak and change you make, you have to save the file and run the test driver, contemplate what you've achieved (usually nothing, or something inexplicably different from what you expect), quit and edit again.

Reason two was that I was fighting my own misconceptions and confusions about the relationship of a QFrame and a QLayout. About the middle of the session I had a satori. Suddenly I understood.

Instead of explaining my hours (indeed weeks) of confusion, I'll just explain how I understand it now. Here's the problem: you have a group of widgets that you'd like to display inside a frame, a nice sunken or raised panel, say. But you also need to arrange them, and your arrangements might need to be two or more levels deep. Here's an example.

For the Find panel, the controls related to actual searching are as follows:

  • Four QCheckBox widgets that condition the interpretation of the search string: Respect Case, Whole Word, Regex, and In Selection. These are laid out in a row in a QHBoxLayout.
  • A customized QLineEdit for entering the search string, and to its left, a QToolButton that pops up a menu of the last 10 search strings used. These two are laid out in a QHBoxLayout.
  • Four QPushButtons that initiate searching named First, Next, Prior and Last. These are laid out as two pairs, First and Next, space, Prior and Last, in a row with a QHBoxLayout.

The three QHBoxLayouts are then stacked in a single QVBoxLayout to keep them together. Call that the_find_box.

I want to group all these ten widgets inside a styled frame. How to do this? OK, you are an experienced Qt hacker and you could do it in your sleep. But if you have not got the background of experience, the answer is frustratingly hard to find. I knew about the layout classes and how to use them to get widgets arranged as I want them horizontally and vertically. But doc pages for the layout objects never talk about frames. And the QFrame doc page doesn't talk about layouts.

The obvious thing would be to create a frame and style it using its methods, and then add it to the layout in much the way you add widgets, or stretch. Nope, doesn't happen. There is no explicit, documented way to connect a layout to a frame, nor vice versa. The word "frame" doesn't appear on the QLayout page, nor "layout" on QFrame.

Here's the answer. Set up this part of the UI in the following sequence:

  • Create the individual buttons, line-edits, labels and checkboxes.
  • Create the sub-layout objects, like the QHBoxLayout that holds the four QCheckBoxes mentioned above. Add the widgets to these.
  • Create the QFrame and set its properties as desired.
  • Create the outermost layout object, the one whose shape should be defined by the frame style, and make the frame its parent.

Using the example above,

    the_find_frame = QFrame()
    the_find_frame.setFrameStyle(QFrame.Sunken)
    the_find_box = QVBoxLayout(the_find_frame) # outermost layout is child of frame
    the_find_box.addLayout(the_checkbox_row)
    ...

That was the key realization: that you link a layout object to a visible frame by making the frame its parent. This makes no kind of software design sense to me. I don't see how a frame, which has only visible style properties, is an appropriate parent to a layout, which has powerful organizational properties. If anything, the relationship should run the other way, with the frame being a child, i.e. a subordinate, of a layout. If the way of connecting the two were to use a QLayout method "addFrame" it would make perfect sense to me.

But in fact, this the arbitrary way you assign a visible "look" to a collection of widgets organized by a layout: by making the QFrame the parent of the outermost QLayout.

No comments: