|  | Home · Examples

# Qt Tutorial 8 - Preparing for Battle

[Previous: Qt Tutorial 7 - One Thing Leads to Another][Qt Tutorial][Next: Qt Tutorial 9 - With Cannon You Can]

In this example, we introduce the first custom widget that can paint itself. We also add a useful keyboard interface (with two lines of code).

## Line by Line Walkthrough

### t8/lcdrange.h

t8/lcdrange.h This file is very similar to the lcdrange.h in Chapter 7. We have added one slot: `setRange()`.

        void setRange(int minValue, int maxValue);

We now add the possibility of setting the range of the `LCDRange`. Until now, it has been fixed at 0 to 99.

### t8/lcdrange.cpp

t8/lcdrange.cpp There is a change to the constructor (we'll discuss that later).
The following code example is written in c++.

    void LCDRange::setRange(int minValue, int maxValue)
    {
        if (minValue < 0 || maxValue > 99 || minValue > maxValue) {
            qWarning("LCDRange::setRange(%d, %d)\n"
                     "\tRange must be 0..99\n"
                     "\tand minValue must not be greater than maxValue",
                     minValue, maxValue);
            return;
        }
        slider->setRange(minValue, maxValue);

    }

The `setRange()` slot sets the range of the slider in the `LCDRange`. Because we have set up the QLCDNumber to always display two digits, we want to limit the possible range of `minVal` and `maxVal` to avoid overflow of the QLCDNumber. (We could have allowed values down to -9 but chose not to.) If the arguments are illegal, we use Qt's qWarning() function to issue a warning to the user and return immediately. qWarning() is a `printf`-like function that by default sends its output to `stderr`. If you want, you can install your own handler function using qInstallMsgHandler().

### t8/cannonfield.h

t8/cannonfield.h `CannonField` is a new custom widget that knows how to display itself.

    class CannonField : public QWidget
    {
        Q_OBJECT

    public:
        CannonField(QWidget *parent = 0);

`CannonField` inherits QWidget. We use the same idiom as for `LCDRange`.

        int angle() const { return currentAngle; }

    public slots:
        void setAngle(int angle);

    signals:
        void angleChanged(int newAngle);

For the time being, `CannonField` only contains an angle value for which we provide an interface using the same idiom as for value in `LCDRange`.

    protected:
        void paintEvent(QPaintEvent *event);

This is the second of the many event handlers in QWidget that we encounter. This virtual function is called by Qt whenever a widget needs to update itself (i.e., paint the widget's surface).

### t8/cannonfield.cpp

t8/cannonfield.cpp
The following code example is written in c++.

    CannonField::CannonField(QWidget *parent)
        : QWidget(parent)
    {

Again, we use the same idiom as for `LCDRange` in the previous chapter.
The following code example is written in c++.

        currentAngle = 45;
        setPalette(QPalette(QColor(250, 250, 200)));
        setAutoFillBackground(true);
    }

The constructor initializes the angle value to 45 degrees and sets a custom palette for this widget.

This palette uses the indicated color as background and picks other colors suitably. (For this widget only the background and text colors will actually be used.) We then call setAutoFillBackground(true) to tell Qt fill the background automatically.

The QColor is specified as a RGB (red-green-blue) triplet, where each value is between 0 (dark) and 255 (bright). We could also have used a predefined color such as Qt::yellow instead of specifying an RGB value.
The following code example is written in c++.

    void CannonField::setAngle(int angle)
    {
        if (angle < 5)
            angle = 5;
        if (angle > 70)
            angle = 70;
        if (currentAngle == angle)
            return;
        currentAngle = angle;
        update();
        emit angleChanged(currentAngle);

    }

This function sets the angle value. We have chosen a legal range of 5 to 70 and adjust the given number of degrees accordingly. We have chosen not to issue a warning if the new angle is out of range.

If the new angle equals the old one, we return immediately. It is important to only emit the `angleChanged()` signal when the angle really has changed.

Then we set the new angle value and repaint our widget. The QWidget::update() function clears the widget (usually filling it with its background color) and sends a paint event to the widget. This results in a call to the paint event function of the widget.

Finally, we emit the `angleChanged()` signal to tell the outside world that the angle has changed. The `emit` keyword is unique to Qt and not regular C++ syntax. In fact, it is a macro.
The following code example is written in c++.

    void CannonField::paintEvent(QPaintEvent *  event )
    {
        QPainter painter(this);
        painter.drawText(200, 200,

                         tr("Angle = ") + QString::number(currentAngle));
    }

This is our first attempt to write a paint event handler. The event argument contains information about the paint event, for example information about the region in the widget that must be updated. For the time being, we will be lazy and just paint everything.

Our code displays the angle value in the widget at a fixed position. To achieve this we create a QPainter operating on the `CannonField` widget and use it to paint a string representation of the `currentAngle` value. We'll come back to QPainter later; it can do a great many things.

### t8/main.cpp

t8/main.cpp
The following code example is written in c++.

    #include "cannonfield.h"

We include the definition of our new class.
The following code example is written in c++.

    class MyWidget : public QWidget
    {
    public:
        MyWidget(QWidget *parent = 0);
    };

The `MyWidget` class will include a single `LCDRange` and a `CannonField`.
The following code example is written in c++.

        LCDRange *angle = new LCDRange;

In the constructor, we create and set up the `LCDRange` widget.
The following code example is written in c++.

        angle->setRange(5, 70);

We set the `LCDRange` to accept angles from 5 to 70 degrees.
The following code example is written in c++.

        CannonField *cannonField = new CannonField;

We create our `CannonField` widget.
The following code example is written in c++.

        connect(angle, SIGNAL(valueChanged(int)),
                cannonField, SLOT(setAngle(int)));

        connect(cannonField, SIGNAL(angleChanged(int)),
                angle, SLOT(setValue(int)));

Here we connect the `valueChanged()` signal of the `LCDRange` to the `setAngle()` slot of the `CannonField`. This will update `CannonField`'s angle value whenever the user operates the `LCDRange`. We also make the reverse connection so that changing the angle in the `CannonField` will update the `LCDRange` value. In our example we never change the angle of the `CannonField` directly; but by doing the last connect() we ensure that no future changes will disrupt the synchronization between those two values.

This illustrates the power of component programming and proper encapsulation.

Notice how important it is to emit the `angleChanged()` signal only when the angle actually changes. If both the `LCDRange` and the `CannonField` had omitted this check, the program would have entered an infinite loop upon the first change of one of the values.
The following code example is written in c++.

        QGridLayout *gridLayout = new QGridLayout;

So far, we have used QVBoxLayout for geometry management. Now, however, we want to have a little more control over the layout, and we switch to the more powerful QGridLayout class. QGridLayout isn't a widget; it is a different class that can manage the children of any widget.

We don't need to specify any dimensions to the QGridLayout constructor. The QGridLayout will determine the number of rows and columns based on the grid cells we populate.

The diagram above shows the layout we're trying to achieve. The left side shows a schematic view of the layout; the right side is an actual screenshot of the program.
The following code example is written in c++.

        gridLayout->addWidget(quit, 0, 0);

We add the Quit button in the top-left cell of the grid, i.e., the cell with coordinates (0, 0).
The following code example is written in c++.

        gridLayout->addWidget(angle, 1, 0);

We put the angle `LCDRange` cell (1, 0).
The following code example is written in c++.

        gridLayout->addWidget(cannonField, 1, 1, 2, 1);

We let the `CannonField` object occupy cells (1, 1) and (2, 1).
The following code example is written in c++.

        gridLayout->setColumnStretch(1, 10);

We tell QGridLayout that the right column (column 1) is stretchable, with a stretch factor of 10. Because the left column isn't (its stretch factor is 0, the default value), QGridLayout will try to let the left-hand widgets' sizes be unchanged and will resize just the `CannonField` when the `MyWidget` is resized.

In this particular example, any stretch factor greater than 0 for column 1 would have the same effect. In more complex layouts, you can use the stretch factors to tell that a particular column or row should stretch twice as fast as another by assigning appropriate stretch factors.
The following code example is written in c++.

        angle->setValue(60);

We set an initial angle value. Note that this will trigger the connection from `LCDRange` to `CannonField`.
The following code example is written in c++.

        angle->setFocus();

Our last action is to set `angle` to have keyboard focus so that keyboard input will go to the `LCDRange` widget by default.

`LCDRange` does not contain any keyPressEvent(), so that would seem not to be terribly useful. However, its constructor just got a new line:
The following code example is written in c++.

        setFocusProxy(slider);

The `LCDRange` sets the slider to be its focus proxy. That means that when someone (the program or the user) wants to give the `LCDRange` keyboard focus, the slider should take care of it. QSlider has a decent keyboard interface, so with just one line of code we've given `LCDRange` one.

## Running the Application

The keyboard now does something: The arrow keys, Home, End, PageUp, and PageDown all do something sensible.

When the slider is operated, the `CannonField` displays the new angle value. Upon resizing, `CannonField` is given as much space as possible.

## Exercises

Try to resize the window. What happens if you make it really narrow or really squat?

If you give the left-hand column a non-zero stretch factor, what happens when you resize the window?

Leave out the QWidget::setFocus() call. Which behavior do you prefer?

Try to change "Quit" to "&Quit". How does the button's look change? (Whether it does change or not depends on the platform.) What happens if you press Alt+Q while the program is running?

Center the text in the `CannonField`.