4.3. Using Child Widgets

You should use the KDE and Qt widgets provided in the respective libraries as children of your custom widgets wherever they would be useful and appropriate. By doing so, you save on development time and reduce the overall memory footprint of your application. If the user is running your application under KDE (or is running another KDE-based application), your application will be sharing the KDE and Qt libraries with the programs already using them. The code you write from scratch is not shared and thus increases the overall memory use of your application + KDE.

You already saw in Chapter 2 how child widgets are used by KTMainWindow. The menubar, toolbar, status line, and content area are all children of KTMainWindow.

Now you will design a simple widget, called KChildren, that creates children and connects them to each other using the signal/slot mechanism to deliver a functioning, custom widget. All this widget's functionality, therefore, comes from its child widgets! The widget is shown in Figure 4.2, and its code is given in Listings 4.5–4.7.


Figure 4.2. The KChildren widget shows how to use child widgets to easily create custom widgets.



Example 4.5. kchildren.h Contains the Class Declaration for KChildren, a Custom Widget

   1 
   2 #ifndef __KCHILDREN_H__
   3 #define __KCHILDREN_H__
   4 
   5 /**
   6  * KChildren
   7  * Create and connect some child widgets.
   8  **/
   9 
  10 #include <qwidget.h>
  11 
  12 class KChildren : public QWidget
  13 {
  14  public:
  15   KChildren (QWidget *parent, const char *name=0);
  16 };
  17 
  18 #endif

This class declaration (Listing 4.5) is perhaps the simplest you could imagine for KDE widget, yet the widget is still functional.

The next listing, Listing 4.6, shows the class definition that, in this case, consists mainly of the definition of the class constructor.


Example 4.6. kchildren.cpp is the Class Definition for KChildren

   1 
   2 1: #include <qlcdnumber.h>
   3 2: #include <qslider.h>
   4 3:
   5 4: #include "kchildren.h"
   6 5:
   7 6: KChildren::KChildren (QWidget *parent, const char *name) :
   8 7:   QWidget (parent, name)
   9 8: {
  10 9:
  11 10:   QLCDNumber *qlcdnumber = new QLCDNumber (2, this);
  12 11:   qlcdnumber->display (0);
  13 12:   qlcdnumber->setGeometry (10, 10, 100, 150);
  14 13:
  15 14:   QSlider *qslider = new QSlider (Qt::Horizontal, this);
  16 15:   qslider->setGeometry (10, 165, 100, 10);
  17 16:
  18 17:   connect ( qslider, SIGNAL (valueChanged (int)),
  19 18:         qlcdnumber, SLOT (display (int)) );
  20 19:
  21 20: }

This widget creates an LCD number and slider as child widgets. The child widgets are managed by the Qt classes QLCDNumber and QSlider, respectively. In Listing 4.6 on line 17, the call to connect() connects the QSlider::valueChanged(int) signal to the QLCDNumber:: display(int) slot so that whenever the user moves the slider, the LCD number is updated. The actual number displayed is determined by QSlider and ranges from 0 at full left to 99 at full right.

In this particular simple widget all the functionality is provided by the KDE/Qt child widgets. In general, you'll have to do a little more work than simply instantiating widgets and connecting them, but the KDE/Qt widgets let you think more about the unique functionality of your application and less about the details of UI components.

Listing 4.7 shows a main() function that can be used to test the function that can be used to test the KChildren widget. Following convention, this program would be compiled to an executable called kchildrentest.


Example 4.7. main.cpp is a main() Function Suitable for Testing the KChildren Widget

   1 
   2 #include <kapp.h>
   3 #include "kchildren.h"
   4 int
   5 main (int argc, char *argv[])
   6 {
   7   KApplication kapplication (argc, argv, "kchildrentest");
   8   KChildren *kchildren = new KChildren (0);
   9   kapplication.setMainWidget (kchildren);
  10   kchildren->show();
  11   return kapplication.exec();
  12 }

4.3.1. Geometry Management

In this widget you do need to do a little more than create and connect the widgets, as I alluded to previously. You need to position them (relative to the main widget, KChildren) and set their size. This is called geometry management. In KChildren the geometry management was performed by placing the widgets at fixed, hard coded positions and giving them fixed sizes. For example, line 12 of Listing 4.6,


   1 
   2 qlcdnumber->setGeometry (10, 10, 100, 150);

places the LCD number widget's upper-left corner at 10 pixels to the right and 10 pixels down from the KChildren widget's upper-left corner. The LCD number widget has a width of 100 pixels and a height of 150 pixels.

This is poor geometry management. Why? Try resizing the window. Notice that the child widgets are unaffected—even if you resize the window so small that the child widgets cannot be accessed (see Figure 4.3). Proper geometry management should take into account the size of the parent widget and the size requirements of the child widgets. (A widget may, for example, need to be of some minimum size before it can be drawn in a reasonably useful or recognizable way.)


Figure 4.3. The KChildren widget does not adapt to different-sized windows because it uses poor geometry management.


Qt provides two geometry managers that can take care of this task for you in most cases. They are QBoxLayout and QGridLayout. The former looks at your widgets as a horizontal or vertical string of widgets, and the latter places your widgets on a grid. QGridLayout is the more flexible of the two, and I will show you an example using it.

Note

Use a geometry manager class to organize your widgets instead of hard coding pixel values.

The widget KTicTacToe is presented in Listings 4.8–4.10 It creates a tic-tac-toe game board by arranging nine KXOSquare widgets in a 33 grid. See Figure 4.4 for a screen shot of the widget.

The first listing, Listing 4.8, shows the class declaration. Most of the work is done in the constructor, but you declare one slot, processClicks(), which will interpret the user's mouse clicks.


Figure 4.4. The KTicTacToe widget uses the KXOSquare widget nine times to create a game board.



Example 4.8. ktictactoe.h is the Class Declaration for the Widget KTicTacToe

   1 
   2 1: #ifndef __KTICTACTOE_H__
   3 2: #define __KTICTACTOE_H__
   4 3:
   5 4: #include <qarray.h>
   6 5: #include <qwidget.h>
   7 6:
   8 7: #include "kxosquare.h"
   9 8:
  10 9: /**
  11 10:  * KTicTacToe
  12 11:  * Draw and manage a Tic-Tac-Toe board using KXOSquare.
  13 12:  **/
  14 13: class KTicTacToe : public QWidget
  15 14:{
  16 15:  Q_OBJECT
  17 16:
  18 17:  public:
  19 18:   /**
  20 19:    * Create an empty game board.
  21 20:    **/
  22 21:   KTicTacToe (QWidget *parent, const char *name=0);
  23 22:
  24 23:
  25 24:  protected slots:
  26 25:    /**
  27 26:     * Process user input.
  28 27:     **/
  29 28:    void processClicks (KXOSquare *, KXOSquare::State);
  30 29:
  31 30: };
  32 31:
  33 32: #endif

At the top of the grid is a QLabel that displays the title "Tic-Tac-Toe." The grid that you create with QGridLayout has 4 rows and 3 columns. Three rows are for the game board, and 1 extra row at the top is for the title. The title (the QLabel) spans all 3 columns.

This work is done in the constructor, given in Listing 4.9.


Example 4.9. ktictactoe.cpp is the Class Definition for KTicTacToe

   1 
   2 1: #include <qlayout.h>
   3 2: #include <qlabel.h>
   4 3:
   5 4: #include "ktictactoe.moc"
   6 5:
   7 6: KTicTacToe::KTicTacToe (QWidget *parent, const char *name) :
   8 7:   QWidget (parent, name)
   9 8: {
  10 9:   int row, col;
  11 10:
  12 11:   QGridLayout *layout = new QGridLayout (this, 4, 3);
  13 12:
  14 13:   const int rowlabel0 = 0, rowlabel1 = 0, collabel0 = 0, collabel1 = 2,
  15 14:     rowsquares0 = 1, rowsquares1 = 4, colsquares0 = 0, colsquares1 = 3;
  16 15:
  17 16:   for (row=rowsquares0; row<rowsquares1; row++)
  18 17:     for (col=colsquares0; col<colsquares1; col++)
  19 18:       {
  20 19:     KXOSquare *kxosquare = new KXOSquare (this);
  21 20:     layout->addWidget (kxosquare, row, col);
  22 21:     connect ( kxosquare,
  23 22:           SIGNAL (changeRequest (KXOSquare *, KXOSquare::State)),
  24 23:           SLOT (processClicks (KXOSquare *, KXOSquare::State)) );
  25 24:       }
  26 25:
  27 26:   QLabel *label = new QLabel ("Tic-Tac-Toe", this);
  28 27:   label->setAlignment (Qt::AlignCenter);
  29 28:   label->setMinimumSize (label->sizeHint());
  30 29:   layout->addMultiCellWidget (label,
  31 30:                   rowlabel0, rowlabel1,
  32 31:                   collabel0, collabel1);
  33 32: }
  34 33:
  35 34:
  36 35: void
  37 36: KTicTacToe::processClicks (KXOSquare *square, KXOSquare::State state)
  38 37: {
  39 38:   //In this simple example, just pass along the click to the appropriate
  40 39:   // square.
  41 40   square->newState (state);
  42 41: }

The layout manager, of type QLayout, is not a widget itself. On line 19, each child widget is created with KTicTacToe as its parent.

A widget typically is added to the layout with QGridLayout::addWidget(). For example, you add a KXOSquare to the layout with


   1 
   2 layout->addWidget (kxosquare, row, col);

in line 20 of Listing 4.9.

You may break the strict grid structure of your widget by using QGridLayout:: addMultiCellWidget(), as you did with QLabel (lines 29-31):


   1 
   2  layout->addMultiCellWidget (label, rowlabel1, rowlabel2,
   3                              collabel1, collabel2);

This call adds QLabel to the grid so that it spans column collabel1 to column collabel2, or column 0 to column 2. Widgets can also call multiple rows. If rowlabel1 and rowlabel2 had different values, this call would make the QLabel span row rowlabel1 to row rowlabel2.

Now try resizing the window. The title text moves itself so that it is always centered, as requested on line 27 of Listing 4.9.

The squares resize themselves to fit their parent widget, which, in turn, fills the window (see Figure 4.5). If you shrink the window very small, the squares nearly disappear, but the text remains totally visible (see Figure 4.6). This is because you set the minimum size of the QLabel in line 28 of Listing 4.9 with


   1 
   2 label->setMinimumSize (label->sizeHint());

The QSize class returned by label->sizeHint() contains the size of the rectangle needed to comfortably contain the text. You didn't set any minimum size for the squares, so they are content simply to disappear as the window is made ever smaller.

You have used constants (for example, rowlabel1, rowsquare1) to describe the layout of the widgets on the grid instead of hard coding the values in the calls to addWidget() and addMultiCellWidget(). This keeps the specification of the layout of the entire grid in one location, lines 13 and 14 of Listing 4.9, making future changes to it easier.


Figure 4.5. The KTicTacToe widget adapts to different-sized windows because it uses Qt's geometry management.



Figure 4.6. The geometry manager was asked not to let the text label shrink too much. No such request was made for the game board, so it nearly disappears when you shrink the window too much.


4.3.2. Playing the Game

This is quite a simple version of Tic-Tac-Toe. The KTicTacToe widget doesn't enforce the rules and doesn't declare a winner! The slot KTicTacToe::processClicks() is the place for this type of logic. The KXOSquare::changeRequest() signal thus serves as a hook into the KXOSquare widget, allowing you to intercept the simple "click ==> draw X or O" logic and apply arbitrary logic to the widget's functioning. This is one way to make a widget more general and thus useful to more developers. To simplify the interface, you might add a (bool) flag to the constructor's argument list, which, when true, causes the constructor to connect the changeRequest() signal to the newState() slot. (Note: In this particular case, the arguments of the signal and slot don't match, so some intermediate slot, which called newState() in KXOSquare, is necessary.) If the flag had a default value of true, the simplest usage of KXOSquare gives the simplest behavior. More sophisticated behavior could still be achieved by sending false for the flag's value and managing the signal as you did in KTicTacToe.