1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>
Qt Tutorial - Chapter 8: Preparing for Battle
</title></head><body bgcolor="#ffffff">
<p>
<table width="100%">
<tr><td><a href="index.html">
<img width="100" height="100" src="qtlogo.png"
alt="Home" border="0"><img width="100"
height="100" src="face.png" alt="Home" border="0">
</a><td valign=top><div align=right><img src="dochead.png" width="472" height="27"><br>
<a href="classes.html"><b>Classes</b></a>
-<a href="annotated.html">Annotated</a>
- <a href="hierarchy.html">Tree</a>
-<a href="functions.html">Functions</a>
-<a href="index.html">Home</a>
-<a href="topicals.html"><b>Structure</b></a>
</div>
</table>
<p>
<h1 align=center>Chapter 8: Preparing for Battle</h1><br clear="all">
<p>
<center><img src="t8.png" alt="Screenshot of tutorial eight"></center>
<p>
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).
<p>
<ul>
<li><a href="t8-lcdrange-h.html">lcdrange.h</a> contains the LCDRange
class definition
<li><a href="t8-lcdrange-cpp.html">lcdrange.cpp</a> contains the LCDRange
implementation
<li><a href="t8-cannon-h.html">cannon.h</a> contains the CannonField class
definition
<li><a href="t8-cannon-cpp.html">cannon.cpp</a> contains the CannonField
implementation
<li><a href="t8-main-cpp.html">main.cpp</a> contains MyWidget and main.
<li><a href="t8-makefile.html">Makefile</a> contains some rules for
generating the meta object information necessary for <a
href="signalsandslots.html">signal/slot creation.</a>
</ul>
<p>
<h2>Line by Line Walk-Through</h2>
<p>
<h3><a href="t8-lcdrange-h.html">lcdrange.h</a></h3>
<p>
This file is very similar to the lcdrange.h in chapter 7. We have added
one slot, setRange(). <pre>
void setRange( int minVal, int maxVal );
</pre>
<p>
We now add the possibility of setting the range of the LCDRange.
Until now, it has been fixed at 0..99.
<p>
<h3><a href="t8-lcdrange-cpp.html">lcdrange.cpp</a></h3>
<p>
There is a change to the constructor, we'll discuss that later. <pre>
void LCDRange::setRange( int minVal, int maxVal )
{
if ( minVal < 0 || maxVal > 99 || minVal > maxVal ) {
<a href="qapplication.html#290ef4">qWarning</a>( "LCDRange::setRange(%d,%d)\n"
"\tRange must be 0..99\n"
"\tand minVal must not be greater than maxVal",
minVal, maxVal );
return;
}
slider->setRange( minVal, maxVal );
}
</pre>
<p>
setRange() sets the range of the slider in the LCDRange. Since we
have set up the QLCDNumber to always display two digits, we want to
limit the possible range of <code>minVal</code> and <code>maxVal</code> to 0..99 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 <code>stderr.</code> You can install your own handler
function using <a href="qapplication.html#a8a31b">::qInstallMsgHandler()</a> if you want.
<p>
<h3><a href="t8-cannon-h.html">cannon.h</a></h3>
<p>
CannonField is a new custom widget that knows how to display itself. <pre>
class CannonField : public QWidget
{
Q_OBJECT
public:
CannonField( <a href="qwidget.html">QWidget</a> *parent=0, const char *name=0 );
</pre>
<p>
CannonField inherits QWidget and we use the same idiom as for LCDRange. <pre>
int angle() const { return ang; }
<a href="qsizepolicy.html">QSizePolicy</a> sizePolicy() const;
public slots:
void setAngle( int degrees );
signals:
void angleChanged( int );
</pre>
<p>
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. <pre>
protected:
void paintEvent( <a href="qpaintevent.html">QPaintEvent</a> * );
</pre>
<p>
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).
<p>
<h3><a href="t8-cannon-cpp.html">cannon.cpp</a></h3> <pre>
CannonField::CannonField( <a href="qwidget.html">QWidget</a> *parent, const char *name )
: <a href="qwidget.html">QWidget</a>( parent, name )
{
</pre>
<p>
Again, we use the same idiom as for LCDRange in the previous chapter. <pre>
ang = 45;
<a href="qwidget.html#d7e4b9">setPalette</a>( <a href="qpalette.html">QPalette</a>( <a href="qcolor.html">QColor</a>( 250, 250, 200) ) );
}
</pre>
<p>
The constructor initializes the angle value to 45 degrees, and sets a
custom palette for this widget.
<p>
This palette uses the indicated color as background, and picks other
colors suitably. (For this widget, only the background and text
colors will be used.) <pre>
void CannonField::setAngle( int degrees )
{
if ( degrees < 5 )
degrees = 5;
if ( degrees > 70 )
degrees = 70;
if ( ang == degrees )
return;
ang = degrees;
<a href="qwidget.html#7569b1">repaint</a>();
emit angleChanged( ang );
}
</pre>
<p>
This function sets the angle value. We have chosen a legal range of
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.
<p>
If the new angle equals the old one, we return immediately. It is
important to only emit the signal angleChanged() when the angle <em>really</em> has changed.
<p>
Then we set the new angle value and repaint our widget. The <a href="qwidget.html#7569b1">QWidget::repaint()</a> function clears the widget (usually filling it with
its background color) and send a paint event to the widget. This
results in a call the paint event function of the widget.
<p>
Finally, we emit the angleChanged() signal to tell the outside world
that the angle has changed. The <code>emit</code> keyword is unique to Qt and
not regular C++ syntax. In fact, it is a macro. <pre>
void CannonField::paintEvent( <a href="qpaintevent.html">QPaintEvent</a> * )
{
<a href="qstring.html">QString</a> s = "Angle = " + QString::number( ang );
<a href="qpainter.html">QPainter</a> p( this );
p.<a href="qpainter.html#0f088f">drawText</a>( 200, 200, s );
}
</pre>
<p>
This is our first attempt to write a paint event handler. The event
argument contains a description of the paint event. <a href="qpaintevent.html">QPaintEvent</a>
contains the region in the widget that must be updated. For the time
being, we will be lazy and just paint everything.
<p>
Our code displays the angle value in the widget at a fixed position.
First we create a QString with some text and the angle, then we create
a QPainter operating on this widget, and use it to paint the string.
We'll come back to QPainter later; it can do a great many things.
<p>
<h3><a href="t8-main-cpp.html">main.cpp</a></h3> <pre>
#include "cannon.h"
</pre>
<p>
We include our new class. <pre>
class MyWidget: public QWidget
{
public:
MyWidget( <a href="qwidget.html">QWidget</a> *parent=0, const char *name=0 );
};
</pre>
<p>
This time, we include a single LCDRange and a CannonField in our top level
widget. <pre>
LCDRange *angle = new LCDRange( this, "angle" );
</pre>
<p>
In the constructor, we create and set up our LCDRange. <pre>
angle->setRange( 5, 70 );
</pre>
<p>
We set the LCDRange to accept ranges from 5 to 70 degrees. <pre>
CannonField *cannonField
= new CannonField( this, "cannonField" );
</pre>
<p>
We create our CannonField. <pre>
<a href="qobject.html#7f8e37">connect</a>( angle, SIGNAL(valueChanged(int)),
cannonField, SLOT(setAngle(int)) );
<a href="qobject.html#7f8e37">connect</a>( cannonField, SIGNAL(angleChanged(int)),
angle, SLOT(setValue(int)) );
</pre>
<p>
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.
<p>
This illustrates the power of component programming and proper
encapsulation.
<p>
Notice how important it is to only emit the angleChanged() signal 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. <pre>
<a href="qgridlayout.html">QGridLayout</a> *grid = new <a href="qgridlayout.html">QGridLayout</a>( this, 2, 2, 10 );
//2x2, 10 pixel border
</pre>
<p>
Until now we've used the no-assembly-required QVBox and QGrid widgets
for geometry management. However, now we want to have a little more
control over the layout, and switch to the more powerful QGridLayout
class. QGridLayout isn't a widget, it is a different class that can
manage the children of <em>any</em> widget.
<p>
As the comment indicates, we create a two-by-two array, with ten pixel
borders. (The constructor for <a href="qgridlayout.html">QGridLayout</a> can be a little cryptic,
so it's good to put in such comments.) <pre>
grid-><a href="qgridlayout.html#dac29c">addWidget</a>( quit, 0, 0 );
</pre>
<p>
We add the Quit button in the top-left cell of the grid: 0, 0. <pre>
grid-><a href="qgridlayout.html#dac29c">addWidget</a>( angle, 1, 0, Qt::AlignTop );
</pre>
<p>
We put the angle LCDRange in the bottom-left cell, aligned to the top
of its cell. (This alignment is one of the things QGridLayout allows
but QGrid does not.) <pre>
grid-><a href="qgridlayout.html#dac29c">addWidget</a>( cannonField, 1, 1 );
</pre>
<p>
We put the CannonField object in the bottom right cell. (The top
right cell is empty, yes.) <pre>
grid-><a href="qgridlayout.html#df80c4">setColStretch</a>( 1, 10 );
</pre>
<p>
We tell QGridLayout that the right column (column 1) is stretchable.
Since the left column isn't (it has stretch factor 0 - the default
value), QGridLayout will try to let the left-hand widgets' sizes be
unchanged and resize just the CannonField when the MyWidget is
resized. <pre>
angle->setValue( 60 );
</pre>
<p>
We set an initial angle value. Note that this will trigger the
connection from LCDRange to CannonField. <pre>
angle-><a href="qwidget.html#25775a">setFocus</a>();
</pre>
<p>
And our last action is to set <code>angle</code> to have keyboard focus, so that
by default, keyboard input will go to the LCDRange widget.
<p>
LCDRange does not contain any keyPressEvent(), so that would seem not
to be terribly useful. However, its constructor just got a new line: <pre>
<a href="qwidget.html#cddadb">setFocusProxy</a>( slider );
</pre>
<p>
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 give LCDRange
one.
<p>
<h2>Behavior</h2>
<p>
The keyboard now does something - the arrow keys, Home, End, PageUp
and PageDown now all do something vaguely sensible.
<p>
When the slider is operated, the CannonField displays the new angle
value. Upon resize, CannonField is gives as much space as possible.
<p>
On Windows machines with an 8-bit display, the new background color is
dithered to death. The next chapter works around this.
<p>
<h2>Exercises</h2>
<p>
Try to resize the window. What happens if you make it really narrow
or really squat?
<p>
If you remove the AlignTop, what happens to the LCDRange's position
and size? Why?
<p>
If you give the left-hand column a non-zero stretch factor, what
happens when you resize the window?
<p>
Leave out the setFocus() call. Which behavior do you prefer?
<p>
Try to change "Quit" to "&Quit" in the QButton::setText() call. How
does the button's look change? What happens if you press Alt-Q while
the program's running? (Meta-Q on a few keyboards.)
<p>
Center the text in the CannonField.
<p>
You may now go on to <a href="t9.html">chapter nine.</a>
<p>
[<a href="t7.html">Previous tutorial</a>]
[<a href="t9.html">Next tutorial</a>]
[<a href="tutorial.html">Main tutorial page</a>]
<p><address><hr><div align="center">
<table width="100%" cellspacing="0" border="0"><tr>
<td>Copyright 2001 Trolltech<td><a href="http://www.trolltech.com/trademarks.html">Trademarks</a>
<td align="right"><div align="right">Qt version 2.3.2</div>
</table></div></address></body></html>
|