Showing posts with label layout. Show all posts
Showing posts with label layout. Show all posts

Thursday, 25 April 2013

Qt Layout Tutorial - Part III

And here we are, in the third - and last - part of our Qt layout tutorial.

As I was preparing this post, I realized one of the group boxes we added in part I, namely the group box corresponding to area B, is not actually necessary. So, we're going to remove it (you may want to refer to part I of this tutorial, since I'll be using the names I defined there):
  • Drag group box B1 to area A. The group box corresponding to area A should "light up" its entire area, to show it has the focus and that's where you'll drop the dragged widgets.
  • Do the same with group box B2 and the vertical spacer (assuming it didn't disappear, see part II).
  • Delete the now empty group box on the right. If you did this before performing the previous steps, you'd delete the group box and everything it contains.
  • Place the group boxes and spacer back where they were.
A very important note: Before you start applying layouts, make sure area B is the exact size you want it to be. When you apply layouts in area B, it should cause a minimum amount of resizing.

So, let's finish this.

As we apply layouts, all the already existing layouts should become perfectly aligned and identically sized, and the widgets' size and position should keep their expected values. If that's not the case, then you need to review the properties on the widgets.

1. Select group box B1 and apply a vertical layout. Do the same with group box B2.

You may have to do some tweaking at this point - e.g., I've had to tweak the spacer in the 2nd row of B1, in order to get the 3 push buttons to the same size as the buttons in B2.

Notice that the push button in row 6 of B2 fills one whole row. If you don't like it, you have several options:
  • If you want the button aligned to the left of the group box, change its sizePolicy.HorizontalPolicy to "Fixed" or "Maximum". You can then set the button's minimumSize.Width to set its width.
  • If you want the button in the center of the row, you'll have to add 1 horizontal spacer to each side of the button. However, you'll have to do this BEFORE you apply the vertical layout to group box B2, because once the button is in a vertical layout, anything you add can only go above or below the button. So, just press "undo", add the 2 spacers, select all 3 widgets, and apply a horizontal layout.
  • If you want the button aligned to the right, follow the procedure in the previous point, but add only 1 spacer, to the left of the button.
If you add spacers, remember to set their sizeType to "Minimum", or area B will expand later.

2. Select the two groupboxes (B1 and B2) and the vertical spacer on the right side of the form and apply a vertical layout. This should give you both group boxes perfectly aligned, one below the other, and all the layouts within each group box (and, consequently, the widgets within each layout) should also be perfectly sized/aligned. If it doesn't - e.g., if some push buttons become larger than others, then... you've guessed it, it's time for some more tweaking.

I needed some tweaking on 4 of the push buttons that were aligned at the right side, and I've decided to change the maximumSize.Width on these buttons, instead of fine-tuning the spacers.

Area B is done, now let's turn to area A. My original idea was that it would contain a single widget, e.g., a label, that would grow and shrink as we resized the form. However, I've decided to complicate matters a bit. Instead, it will contain two widgets, a list widget on top of a text edit widget. The list will resize horizontally, but not vertically; and the text edit will resize both vertically and horizontally.

3. Add a list widget and a text edit to group box A.

4. Select the list. Its sizePolicy.HorizontalPolicy should be "Expanding". We'll set its sizePolicy.VerticalPolicy to "Minimum".

5. Now, select the text edit. Both sizePolicy.HorizontalPolicy and sizePolicy.VerticalPolicy should be "Expanding".

6. Select group box A and apply a vertical layout.

When you resize group box A, this is what should happen:
  • When the group box resizes horizontally, both widgets should resize accordingly.
  • When the group box expands vertically, the list may expand, but should stop doing so at some point, and only the text edit should keep expanding.
  • When the group box shrinks vertically, the edit box should shrink and, after some point, the list should begin shrinking, too.
When we apply all layouts, the list will stop resizing vertically.

Area A is done. It was a lot simpler because we didn't want to fine-tune our control of the widgets in this area. Keep this in mind when designing your UIs - the less control you want over size, position, and resizing, the better.

7. Before we turn to area C, let's select areas A and B and apply a horizontal layout. When you resize this layout, area B should move, but always keep the same size, and area A should maintain the resize rules outlined above.

Also notice that as you shrink this layout, either vertically or horizontally, all the widgets eventually get shrunk to oblivion. Don't worry, this will be solved soon.

Now, area C. Since I don't know very well what I could place here, I'll keep it simple - just a text edit that will resize horizontally, but not vertically.

8. Add a text edit to area C. Set its sizePolicy.VerticalPolicy to "Minimum".

9. Select the group box C and apply a horizontal layout.

10. Finally, select the form or its top widget (e.g., if it's a QMainWindow, you can select the main window or the central widget) and apply a vertical layout.

Build and run! You may need some additional touches. I did, had to tweak the sizeHint.Width on spacers to get all the buttons properly aligned.

Anyway, it's ready! Get a liquid beverage (as opposed to a "solid beverage", obviously) of your choice and indulge in as much rejoicing as legally permitted. And, while indulging in said rejoicing, notice that if you try to shrink the form it eventually stops - e.g., area B acts as a vertical break, it will stop shrinking when the vertical spacer on bottom reaches 0.

I hope you've found it useful, and not overly boring.

I'll end by saying, once again, that this tutorial is based mostly on trial and error. While I've advised certain values for widget properties, the same effect can be achieved using different values. And, naturally, there may even be a faster/better way of achieving this. And, if you happen to know of such a faster/better, you're welcome to drop me a line on the comments.

You can find the files here.

Sunday, 21 April 2013

Qt Layout Tutorial - Part II

Note: Now that the tutorial is finished, here are links to part I, part III, and the files.

Before we proceed with our tutorial, a few notes about maximumSize:
  • Qt Designer won't let you resize a widget beyond its maximumSize. So, when you want to experiment with it, make sure its value is someHugeValueDefinitelyLargerThan42 (don't ask, just read part I), so you can resize it to your heart's content.
  • If you want your widget to expand with the layout, you'll have to take that into account when you change maximumSize. In this example, since I don't want area B to expand, I'm setting maximumSize equal to the current size.
  • Even though I said you don't need to change anything on push buttons, setting their maximumSize is always an option if you're having trouble getting them to keep your desired size.
Also, if you're having trouble with widget sizes in different layouts, the first thing to do is resize the layouts so they have similar dimensions. This is especially helpful with the push buttons in this tutorial, if you don't want to change any of their properties.

One final note: For some reason I didn't figure out (but I admit I didn't do much research on it), the vertical spacer I placed on step 3 of part I went missing when I reopened the project for this post. I added it back, closed and re-opened Qt Creator, opened the project and... gone again. I know once we apply a layout (which we'll do in part III) it stays put, so I've just accepted it as another little quirk I'll have to live with.

Now, let's continue our Qt layout tutorial.

In part I we've divided the form in 3 areas, and filled area B1 (you may want to refer to part I, since I've defined these names there). Now, let's take care of area B2.

Area B2 will contain 6 rows of widgets (don't add them yet, just keep reading):
  • label + line edit + label + line edit + label + line edit + horizontal spacer + push button.
  • label + line edit + horizontal spacer + push button.
  • label + line edit + label + line edit + label + line edit + horizontal spacer + push button.
  • label + line edit + horizontal spacer + label + line edit + horizontal spacer.
  • label + combo box.
  • push button.
These widgets will need the same changes as we applied to the widgets we added to B1, otherwise B2 would cause area B to expand. So, instead of adding the widgets and changing all the properties again, I'll copy/paste as much as I can. Naturally, you're encouraged to change the widgets text and size, and play around with it; if a widget doesn't behave as expected when you apply a layout, take a look at its maximumSize and sizePolicy.

Also, don't worry if the layouts aren't perfectly aligned or have small differences in size. When we apply the final layouts in part III, everything will fall into place.

1. B2 - Row 1
1.1. Select a label and a line edit in B1, and copy/paste. Then, drag the copies to B2. Adjust the text and size of both widgets according to what you want. Repeat until you have 3 labels and 3 line edits in B2. Notice that you won't have to change sizePolicy, because the original widgets already had the correct value for our goal.

1.2. Select the horizontal spacer in B1, copy/paste, and drag it to B2. Once again, you won't have to change anything else.

1.3. You can add a new push button, instead of using copy/paste, since we didn't change anything on the push buttons.

2. B2 - Rows 2-6
For row 2, select a label, a line edit, a horizontal spacer and a push button from row 1. Copy/paste, and drag. Row 3 happens to have the exact same widgets as row 1, so you can just copy/paste row 1. For row 4, you can copy/paste a label + edit + spacer twice. And for rows 5 & 6, just add the widgets, setting the combo box's sizePolicy.HorizontalPolicy to "Minimum".

3. Next, we apply a horizontal layout to row 1. Everything should become neatly spaced, within the layout. Then, we apply a horizontal layout to row 2.

Now, since row 2 also has a push button at the right end, I'd like those buttons aligned. In order to achieve that, I aligned both layouts at the left border and fiddled with the sizeHint.Width on row 2's spacer. This is the best process I've discovered to align widgets on a layout - change their size in the property sheet. I did the same with the edit widget on row 2 to align it on the right with the 2nd edit widget on row 1.

4. Row 3 should be easy, since it's identical to row 1, so we just apply a horizontal layout. Row 4, however, may require some tweaking to get a widget alignment that looks good with the other rows. Some options are - change the dimension of the edits in rows 1-3 to align them with the second edit on row 4, or remove the second spacer in row 4 and let the 2nd edit align to the right.

I went with the first option. And that was good, because it showed me again that, while I can get around designing this UI, that doesn't mean I know exactly what's going on. I increased the size on the 3 edits in row 1. Instead of "compressing" the spacer, this sent the push button off the edge of group box. I resized the layout to fit within the group box, and, again, the spacer remained constant and the push button shrank. I only got the spacer to shrink and the push button to return to its "normal" (read "desired") size by manually reducing the spacer's sizeHint.Width on the property inspector.

However, when I did the same thing in row 2, everything went smoothly. Even though the push button was also pushed off, when I resized the layout, it went back into place and the spacer shrank automatically. I even increased the spacer's sizeHint.Width, which pushed the button off again, but when I resized the layout, back into place it went, with no need for manual resizing.

I checked the properties on all the widgets in rows 1 and 2, matching each identical widget - label to label, edit to edit, etc. All size policies were identical, all the mix/max sizes were set in the same fashion. So, why the different behaviour? No idea. At least row 3 behaved like row 1, so whatever's happening, it's consistent.

Then, I set the first spacer on row 4 with sizeType "Maximum" and adjusted the maximumSize.Width on the second edit, until it aligned on the right with the first edit on row 3.

5. Row 5 is easy, we just select both widgets and apply a horizontal layout. Then, we resize the layout, and the combo box will expand. Row 6 is just the push button, so we'll leave it be, there's no need to apply any layout.

That's it for now, we'll wrap it up next time.

Tuesday, 16 April 2013

Qt Layout Tutorial - Part I

Note: Now that the tutorial is finished, here are links to part II, part III, and the files.

Laying out a roadmap

Over the next few posts, I'll show how to get Qt's layouts to do what you want, instead of totally ignoring you and just "doing their thing".

I'll start by saying what this is not:
  • It's not a reference of Qt's widgets/layouts.
  • It's not an introduction to Qt's layouts.
In fact, it may not even be the best/cleanest way to achieve the result I present here. It's just the way I figured out. It's also the tutorial I wish I had found when I had to deal with layouts in Qt, namely, something that explained step-by-step how to create a non-trivial UI in Qt Designer.

We'll be creating an UI similar to this (riveting, heh?):

+-------++----+
|       ||    |
|   A   ||  B |
|       ||    |
+-------++----+
+-------------+
|      C      |
+-------------+

A - Visualization area. When we resize the form, this area expands/shrinks both horizontally and vertically. In this tutorial, it'll probably contain a QLabel (e.g., for image viewing).
B - Input area. This will contain two group boxes, each with several widgets. When we resize the form, this area will move, but won't grow/shrink. This area will anchor on the form's left side.
C - Feedback area. I have nothing defined for this area, and will improvise a bit once we get to it. This area will grow/shrink horizontally, will move vertically, and it will anchor on the form's bottom.

By the way, when I use the word "form", I'm referring to any of the options we can use for a .ui file in Qt - main window, dialog, page, widget, etc.

Let's get started

1. We'll start by creating a new GUI project in Qt Creator. As I said, this is not an introduction, so I expect you know how to create a GUI project in Qt Creator.

2. Add 3 group boxes to the form, with positions roughly similar to areas A, B, and C, above.

3. Add 2 more group boxes to area B, one on top of the other; we'll call them B1 (top) and B2 (bottom). Add a vertical spacer below B2. Since area A will grow vertically and area B won't, we'll use this spacer to fill the remaining space.

4. Add the following to B1, in 2 rows:
  • Top row: label + line edit + label + line edit + horizontal spacer.
  • Bottom row: 3 push buttons.

5. Edit the labels and input your description. You can then resize the labels by selecting them and pressing Ctrl+J. Or right-click and select "Lay out -> Adjust Size". Notice that the labels will adjust to the text you've entered.

6. Next, resize the line edits to a sensible width for what you expect to input. E.g., in my case, I'll be inputting numbers in the range 0-255.

7. Now, we'll apply our first layout. Select the top row (label + line edit + label + line edit + spacer) and apply a horizontal layout. What happens next depends on the width of the form and the widgets; in my case, the labels kept their width, but the edits increased in width and threw the spacer off the form, to the right.

So, what's going on? If you take a look at the labels, their sizePolicy is "Preferred, Preferred". However, for the edits, sizePolicy.HorizontalPolicy is "Expanding". And maximumSize.Width is someHugeValueDefinitelyLargerThan42 (16777215, in my case). We need to put some sane values here.

First, press Ctrl+Z to undo the layout.

We'll start with the labels. Even though they kept their size, I believe it's cleaner to change them to state our intent - change their maximumSize.Width to match their current size, i.e., geometry.Width.

Next, we'll change the edits, setting sizePolicy.HorizontalPolicy to "Minimum", and maximumSize.Width to each edit's geometry.Width.

Now, we reapply the horizontal layout. Et voilĂ . The madness is gone, everything's sane. Well, for now. There's still one change missing, and we'd better do it now. Why? Because if we don't do it now, when we have just a few widgets, we'll have to go through a most-certainly-not-boring-no-no-not-at-all cycle of trial and error later, when we have, say, some 20+ widgets on our form.

So, select the spacer and change its sizeType from "Expanding" to "Minimum". And why are we changing this? Because if we don't, when we apply a layout to the form, area B will expand, and that's not what we want.

So, to recap: After setting the labels and edits to your desired width, set their maximumSize.Width to that value, and set sizePolicy.HorizontalPolicy to "Minimum" for both edits and the spacer.

8. Now, we'll apply our second layout, on the 3 push buttons in the bottom row of B1.

Here, we won't have to do anything else. Even though each button's maximumSize.Width is someHugeValueDefinitelyLargerThan42, their sizePolicy.HorizontalPolicy is "Minimum"; this ensures that these buttons won't cause B1 to expand, which is our goal.

If you want to keep the buttons smaller, add 2 spacers between them. If you want to keep 2 buttons together and separated from the third one, add a spacer between them. Just remember that the spacers on B can't have sizeType "Expanding", as that'll cause B to expand later on. I'm using "Minimum" since it's what worked best for me.

A dose of reality

Even though the steps presented above are nice, clean, and simple, the process to get there was anything but. It involved generous amounts of trial and error, experimenting and fiddling.

I now have a better understanding of how to get the layouts to do what I want, but I still think this is not practical. We shouldn't have to fiddle with each control to get the results we want, there should be overriding policies at both the layout level and the container level. And Qt Designer should allow us to resize the widgets after a layout has been applied and should have options to automatically change the necessary values to keep the appearance we want within the layout.

Yes, I know. This is Open Source, if you don't like it, contribute. The thing is, my sentence above, "I now have a better understanding of how to get the layouts to do what I want", is only half the story; the other half is "but I still don't actually understand how layouts work, I still can't visualize all the parts that contribute to it and grok their interactions". And this is definitely not high on my list of priorities, nor should it be.

Nor should it be, and that is actually my main complaint about the layout managers I've had to deal with, both in Qt and in Java - for something that should be simple and transparent, they sure require a lot of investment just to get it working, once you move away from Trivialville.

Anyway, that's it for now. We'll continue this tutorial on my next post, when we'll add a boatload of widgets to area B2.