Qt Tutorial - Chapter 8

# Chapter 8: Preparing for Battle

In this example, we introduce the first custom widget that can paint itself.

- lcdrange.h contains the LCDRange class definition
- lcdrange.cpp contains the LCDRange implementation
- cannon.h contains the CannonField class definition
- cannon.cpp contains the CannonField implementation
- main.cpp contains MyWidget and main.
- Makefile contains some rules for generating the meta object information necessary for signal/slot creation.

## Line by Line Walk-Through

### lcdrange.h

This file is very similar to the lcdrange.h in chapter 7. We have added one slot, setRange().

            void setRange( int minVal, int maxVal );

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

### lcdrange.cpp

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

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

### cannon.h

CannonField is a new custom widget that knows how to display itself.

        class CannonField : public QWidget
        {
            Q_OBJECT
        public:
            CannonField( QWidget *parent=0, const char *name=0 );

CannonField inherits QWidget and we use the same idiom as for LCDRange.

            int angle() const { return ang; }
        public slots:
            void setAngle( int degrees );
        signals:
            void angleChanged( int );

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 * );

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).

### cannon.cpp

        CannonField::CannonField( QWidget *parent, const char *name )
                : QWidget( parent, name )
        {

Again, we use the same idiom as for LCDRange in the previous chapter.

            ang = 45;
        }

The constructor simply initializes the angle value to 45 degrees.

        void CannonField::setAngle( int degrees )
        {
            if ( degrees < 5 )
                degrees = 5;
            if ( degrees > 70 )
                degrees = 70;
            if ( ang == degrees )
                return;
            ang = degrees;
            repaint();
            emit angleChanged( ang );
        }

This function sets the angle value. We have chosen a legal range 5..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 impotrant to only emit the signal angleChanged() when the angle really has changed.

Then we set the new angle value and repaint our widget. The repaint() function will clear the widget (i.e. fill it with its background color) and send a paint event to the widget. This will immediately call the paint event function of the widget. If you want Qt to send the event later (when it regains control) use the update() function.

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.

        void CannonField::paintEvent( QPaintEvent * )
        {
            QString s;
            s.sprintf( "Angle = %i", ang );
            drawText( 200, 100, s );
        }

This is our first attempt to write a paint event handler. The event argument contains a description of the paint event. QPaintEvent contains the rectangular area in the widget that must be updated. For the time being, we will be lazy and always update the entire widget.

Our code displays the angle value in the widget at a fixed position. First we create a QString object. QString is a Qt's string class (see the documentation for details). Then we set the string using the QString::sprintf() function, which is similar to sprintf(). Finally, we draw the text at position 200,100 (relative to the baseline of the text) in the widget using the QWidget::drawText() function. Normally, you will use a QPainter to draw in a widget, but drawText() is a convenience function for drawing text. In the next chapter, you'll see how QPainter works.

### main.cpp

        #include "cannon.h"

We include our new class.

        class MyWidget : public QWidget
        {
        public:
            MyWidget( QWidget *parent=0, const char *name=0 );
        protected:
            void resizeEvent( QResizeEvent * );
        private:
            QPushButton *quit;
            LCDRange    *angle;
            CannonField *cannonField;
        };

This time, we include a single LCDRange and a CannonField in our top level widget.

            angle  = new LCDRange( this, "angle" );
            angle->setRange( 5, 70 );
            angle->setGeometry( 10, quit->y() + quit->height() + 10, 75, 130 );

In the contructor, we create and set up our LCDRange with a range 5..70 and put it 10 pixels below the quit button and fix its size to 75x130.

            cannonField = new CannonField( this, "cannonField" );
            cannonField->move( angle->x() + angle->width() + 10, angle->y() );
            cannonField->setBackgroundColor( QColor( 250, 250, 200) );

We create and set up our CannonField. Its position is 10 pixels to the right of the LCDRange and the same y position as LCDRange. The size will be set by the resize event.

Then we set the background color of the CannonField. QColor is one of Qt's color classes. Here we hard code an RGB value red=250, green=250 and blue=200 (which gives an easter-like yellow pastel color). The range of the RGB values is 0..255. You can also set up a QColor using the HSV color model.

Another color class, QPalette, provides a complete array of colors, so you can change the colors without messing up the 3D effects.

            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 only emit the angleChanged() signal when the angle actually changes. If both the LCDRange and the CannonField had had this design flaw, the program would have entered an infinite loop upon the first change of one of the values.

            angle->setValue( 60 );

Finally, we set an initial angle value. Note that this will trigger the connection from LCDRange to CannonField.

        void MyWidget::resizeEvent( QResizeEvent * )
        {
            cannonField->resize( width()  - cannonField->x() - 10,
                                 height() - cannonField->y() - 10 );
        }

We give the CannonField all the space we can, except for the 10 pixel border to the bottom and to the right.

## Behavior

When the scroll bar is operated, the CannonField displays the new angle value. Upon resize, CannonField is gives as much space as possible.

On Windows machines with an 8-bit display, the new background color is dithered to death. The next chapter works around this.

## Excercises

Make the position of the printed text dependent on the angle value.

Change the resize event to give maximum space to the LCDRange instead of the CannonField.

You may now go on to chapter nine.

[Previous tutorial] [Next tutorial] [Main tutorial page]