Layout of Widgets

A widget can be popped up on the screen, like a dialog, or it can be made a part of a larger window. Whenever we wish to arrange smaller widgets inside larger ones, we use layouts. A layout is an object that belongs to exactly one widget. Its sole responsibility is to organize the space occupied by its owner's child widgets.

Although each widget has a setGeometry() function that allows you to set its size and position, absolute sizing and positioning are rarely used in a windowing application because they impose an undesirable rigidity on the design. Proportional resizing, splitters, scrollbars when needed, and flexible arrangement of visual space are all achieved quite naturally through the use of layouts.

The process of specifying the way that your widgets will be arranged on the screen consists of dividing the screen into regions, each controlled by a QLayout. Layouts can arrange their widgets

  • Vertically (QVBoxLayout)
  • Horizontally (QHBoxLayout)
  • In a grid (QGridLayout)
  • In a stack where only one widget is visible at any time (QStackedLayout)

Widgets are added to QLayouts using the addWidget() function.

Layouts are not widgets, and they have no visual representation. Qt supplies an abstract base class named QLayout plus several concrete QLayout subclasses: QBoxLayout (particularized to QHBoxLayout and QVBoxLayout), QGridLayout, and QStackedLayout. Each of the layout types has an appropriate set of functions to control the spacing, sizing, alignment, and access to its widgets.

For its geometry management to work, each QLayout object must have a parent that is either a QWidget or another QLayout. The parent of a QLayout can be specified when the layout is constructed by passing the constructor a pointer to the parent widget or layout. It is also possible to construct a QLayout without specifying its parent, in which case you can call QWidget::addLayout() at some later time.

Layouts can have child layouts. One layout can be added as a sub-layout to another by calling addLayout(). The exact signature depends on the kind of layout used. If the parent of a layout is a widget, that widget cannot be the parent of any other layout.

The CardTable class defined in Example 11.10 reuses libcards2, for easy access to QPixmaps of playing cards (see Section 11.4). Constructing a CardTable object puts Figure 11.5 on the screen.

Figure 11.5. Rows and columns

 

Example 11.10. src/layouts/boxes/cardtable.h

#ifndef CARDTABLE_H
#define CARDTABLE_H
#include 
#include 

class CardTable : public QWidget {
 public:
 CardTable();
 private:
 CardDeck m_deck;

};

#endif // #ifndef CARDTABLE_H

CardTable is implemented in Example 11.11 by making use of the fact that a QLabel can hold an image. This implementation demonstrates some simple but useful layout techniques.

Example 11.11. src/layouts/boxes/cardtable.cpp

[ . . . . ]
// Given a pixmap, return a label with that pixmap on it.
static QLabel* label(QPixmap pm) {
 QLabel* retval = new QLabel();
 retval->setPixmap(pm);
 return retval;
}

CardTable::CardTable() {

 // create 2 rows of cards:
 QHBoxLayout *row = new QHBoxLayout();
 row->addWidget(label(m_deck.get(1)));
 row->addWidget(label(m_deck.get(2)));
 row->addWidget(label(m_deck.get(3)));
 row->addWidget(label(m_deck.get(4)));

 QVBoxLayout* rows = new QVBoxLayout();
 rows->addLayout(row);

 row = new QHBoxLayout();
 row->addWidget(label(m_deck.get(5)));
 row->addWidget(label(m_deck.get(6)));
 row->addWidget(label(m_deck.get(7)));
 rows->addLayout(row);

 // create a column of buttons:
 QVBoxLayout *buttons = new QVBoxLayout();
 buttons->addWidget(new QPushButton("Deal"));
 buttons->addWidget(new QPushButton("Shuffle"));

 // Bring them together:
 QHBoxLayout* cols = new QHBoxLayout();
 setLayout(cols); <-- 1
 cols->addLayout(rows); <-- 2
 cols->addLayout(buttons); <-- 3
}
[ . . . .]
 

(1)the "root layout" for this widget

(2)Add both card rows as a column.

(3)Add column of buttons as another column.

The simple piece of client code shown in Example 11.12 suffices to put the window on the screen.

Example 11.12. src/layouts/boxes/boxes.cpp

#include 
#include "cardtable.h"

int main(int argc, char* argv[]) {
 QApplication app (argc, argv);
 CardTable ct;
 ct.show();
 return app.exec();
}

If you build and run this example and use your mouse to resize the window, you will notice that the width of the buttons stretches first to gobble up extra space, but that there is also stretchable spacing between the cards, as well as between the buttons. If we removed the buttons, we could observe that the horizontal spacing between the cards would grow evenly and uniformly.

11.5.1. Spacing, Stretching, and Struts

To get finer control over the layout of widgets, we can use the API of the QLayout class. Box layouts, for example, offer the following functions:

  1. addSpacing(int size) adds a fixed number of pixels to the end of the layout.
  2. addStretch(int stretch = 0) adds a stretchable number of pixels. It starts at a minimum amount and stretches to use all available space. In the event of multiple stretches in the same layout, this can be used as a growth factor.
  3. addStrut(int size) imposes a minimum size to the perpendicular dimension (i.e., the width of a VBoxLayout or the height of an HboxLayout).

Revisiting Example 11.11, we will make the layout behave a little better during resizing. Figure 11.6 shows the results of adding some stretch and some spacers to this application.

Figure 11.6. Improved layout with stretch and spacers

Normally, layouts try to treat all widgets equally. When we want one widget to be off to a side, or pushed away from another, we can use stretches and spacing to deviate from that norm. Example 11.13 demonstrates how to use stretches and spacing.

Example 11.13. src/layouts/stretch/cardtable.cpp

[ . . . . ]
QVBoxLayout *buttons = new QVBoxLayout();

 buttons->addStretch(1); <-- 1
 buttons->addWidget(new QPushButton("Deal"));
 buttons->addWidget(new QPushButton("Shuffle"));
 buttons->addSpacing(20); <-- 2

 QHBoxLayout* cols = new QHBoxLayout();
 setLayout(cols);
 cols->addLayout(rows);
 cols->addStretch(30); <-- 3
 cols->addLayout(buttons);
}
[ . . . . ]
 

(1)stretchable space before buttons in column

(2)fixed spacing after buttons

(3)Adds a fixed spacing of 30 that stretches

If you build and run this application using the code from Example 11.13 instead of Example 11.11, you can resize the main window and observe that the buttons no longer grow, and are pushed off to the corner. The horizontal spacing between the cards does not grow, but the vertical spacing does.

11.5.2. Moving Widgets across Layouts

Figure 11.7 shows the basic layout for our next example, which demonstrates what happens when a widget is added to more than one layout.

Figure 11.7. Moving labels application

This application moves QLabels from one layout to the other in response to the button press. In Example 11.14 we derive from QApplication a class that defines the GUI.

Example 11.14. src/layouts/moving/moving.h

[ . . . . ]

class MovingApp : public QApplication {
 Q_OBJECT
 public:
 MovingApp(int argc, char* argv[]);

 public slots:
 void moveLeft();
 void moveRight();
 void newLeft();
 void newRight();
 private:
 QString nextLabel();
 QMainWindow m_MainWindow;
 QQueue m_LeftQueue, m_RightQueue;
 QVBoxLayout *m_LeftLayout, *m_RightLayout;
 int m_Count;
};
[ . . . . ]

The constructor starts by creating the layouts and the various widgets, as we see in Example 11.15.

Example 11.15. src/layouts/moving/moving.cpp

[ . . . . ]

MovingApp::MovingApp(int argc, char* argv[]) :
 QApplication(argc, argv),
 m_MainWindow(),
 m_Count(0) {

 QWidget *center = new QWidget(&m_MainWindow);
 m_MainWindow.setCentralWidget(center); <-- 1

 QGridLayout *mainGrid = new QGridLayout;

 m_LeftLayout = new QVBoxLayout;
 m_RightLayout = new QVBoxLayout;

 mainGrid->addLayout(m_LeftLayout, 0,0);
 mainGrid->addLayout(m_RightLayout, 0, 1);
 QPushButton *moveRight = new QPushButton("Move Right");
 QPushButton *moveLeft = new QPushButton("Move Left");
 mainGrid->addWidget(moveRight, 1,0);
 mainGrid->addWidget(moveLeft, 1,1);

 QPushButton *addRight = new QPushButton("Add Right");
 QPushButton *addLeft = new QPushButton("Add Left");

 mainGrid->addWidget(addLeft, 2,0);
 mainGrid->addWidget(addRight, 2,1);
 center->setLayout(mainGrid);
 

(1)The QMainWindow takes ownership of this widget and makes it the central widget. We do not need to delete it.

After creation of the various layouts and widgets, signals must be connected to slots, as we see in Example 11.16.

Example 11.16. src/layouts/moving/moving.cpp

[ . . . . ]

 connect(moveRight, SIGNAL(pressed()), this, SLOT(moveRight()));
 connect(moveLeft, SIGNAL(pressed()), this, SLOT(moveLeft()));
 connect(addRight, SIGNAL(pressed()), this, SLOT(newRight()));
 connect(addLeft, SIGNAL(pressed()), this, SLOT(newLeft()));
 // What do the insertStretch lines do?
 m_LeftLayout->insertStretch(0);
 m_RightLayout->insertStretch(0);

 newLeft(); <-- 1
 newRight(); <-- 2
 m_MainWindow.move(200,200);
 m_MainWindow.resize(300, 500);
 m_MainWindow.show();
 

(1)This puts a label in the left layout.

(2)This puts a label in the right layout.

Because a widget cannot exist in more than one layout at any given time, it disappears from the first layout and shows up in the new one. Each widget retains its parent after the layout change. The code for the movement slots is shown in Example 11.17.

Example 11.17. src/layouts/moving/moving.cpp

[ . . . . ]

void MovingApp::moveLeft() {
 if (m_RightQueue.isEmpty()) return;
 QLabel *top = m_RightQueue.dequeue();
 m_LeftQueue.enqueue(top);
 m_LeftLayout->addWidget(top); <-- 1
}

void MovingApp::moveRight() {
 if (m_LeftQueue.isEmpty()) return;
 QLabel *top = m_LeftQueue.dequeue();
 m_RightQueue.enqueue(top);
 m_RightLayout->addWidget(top);
}
 

(1)By adding it to the left, it disappears from the right.

Exercises: Layout of Widgets

1.

There are many ways of getting information from the user. The keeper of the Bridge of Death wants to know the answers to three questions, as we see in the following figure.

  • Create a dialog that asks these questions of the user, using QLineEdit widgets and two QPushButtons to submit or cancel the request. Check the responses to make sure they are correct. If they are not correct, output a funny message.
  • Change the third question randomly so that half of the time it asks "What is the mean air speed velocity of an unladen swallow?"
2.

The 15 puzzle (or n2 - 1 puzzle) involves a 4 x 4 (n x n) grid that contains 15 tiles numbered 1 to 15 (1 to n2 - 1), and one empty space. The only tiles that can move are those next to the empty space.

  • Create a 15 puzzle with QPushButtons in a QGridLayout.
  • At the start of the game, the tiles are presented to the player in "random" order. The object of the game is to rearrange them so that they are in ascending order, with the lowest numbered tile in the upper-left corner.
  • If the player solves the puzzle, pop up a QMessageBox saying "YOU WIN!" (or something more clever).
  • Add some buttons:

    • Shuffle: Randomize the tiles by performing a large number (at least 50) of legal tile slides.
    • Quit: To leave the game.

Design Suggestions

Want to get a head start using the model-view-controller style? It comes up later, but you can try it now. Define the classes shown in the accompanying figure and try to partition your code properly into them.

Model-View-Controller Design for Puzzle


Part I: Introduction to C++ and Qt 4

C++ Introduction

Classes

Introduction to Qt

Lists

Functions

Inheritance and Polymorphism

Part II: Higher-Level Programming

Libraries

Introduction to Design Patterns

QObject

Generics and Containers

Qt GUI Widgets

Concurrency

Validation and Regular Expressions

Parsing XML

Meta Objects, Properties, and Reflective Programming

More Design Patterns

Models and Views

Qt SQL Classes

Part III: C++ Language Reference

Types and Expressions

Scope and Storage Class

Statements and Control Structures

Memory Access

Chapter Summary

Inheritance in Detail

Miscellaneous Topics

Part IV: Programming Assignments

MP3 Jukebox Assignments

Part V: Appendices

MP3 Jukebox Assignments

Bibliography

MP3 Jukebox Assignments



An Introduction to Design Patterns in C++ with Qt 4
An Introduction to Design Patterns in C++ with Qt 4
ISBN: 0131879057
EAN: 2147483647
Year: 2004
Pages: 268

Similar book on Amazon

Flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net