Qt logo


Kapitola 8: Príprava na bitku


Screenshot of tutoriálu č. eight

V tomto príklade po prvýkrát uvádzame widget, ktorý sa dokáže sám nakresliť.

Prechádzka riadok po riadku

lcdrange.h

Tento súbor je takmer rovnaký ako ten v kapitole 7. Pridali sme slot setRange().

        void setRange( int minVal, int maxVal );

Teraz môžeme meniť rozsah LCDRange. Doteraz bol pevne nastavený na 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() nastavuje rozsah rolovacej lišty vo widgete LCDRange. Pretože sme QLCDNumber nastavili tak, aby mal vždy dve číslice, chceme obmedziť možný rozsah hodnôt minVal a maxVal na 0..99, aby sme zabránili pretečeniu QLCDNumber. Mohli sme nastaviť aj hodnoty medzi -9 a 99, ale nechceli sme. Ak budú argumenty mimo povolený rozsah, použijeme Qt funkciu warning() na vyslanie okamžitého upozornenia užívateľovi. warning() funkcia podobná na printf(), ktorá obvykle posiela svoj výstup na stderr. Ak chcete, môžete implementovať Vašu vlastnú obslužnú funkciu.

cannon.h

CannonField je nový widget, ktorý vie, ako sa má sám zobraziť.

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

CannonField je potomkom triedy QWidget a teda použijeme rovnaký spôsob definície ako v LCDRange.

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

CannonField zatiaľ obsahuje iba hodnotu uhla, pre ktorú vytvoríme rovnaké rozhranie ako pre hodnotu v LCDRange.

    protected:
        void paintEvent( QPaintEvent * );

Toto je druhá z viacerých rôznych funkcií na spracovanie udalostí triedy QWidget, ktorú stretávame. Táto virtuálna funkcia je volaná knižnicou Qt vždy, keď sa widget potrebuje upraviť (t.j. prekresliť svoj povrch).

cannon.cpp

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

Opäť, rovnaký konštruktor ako pre LCDRange v predchádzajúcej kapitole.

        ang = 45;
    }

Konštruktor jednoducho inicializuje hodnotu uhla na 45 stupňov.

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

Táto funkcia nastaví hodnotu uhla. Zvolili sme si povolený rozsah medzi 5 a 70, a zadané číslo podľa toho prispôsobíme. Ak je nový uhol mimo rozsah, nebudeme vysielať varovanie.

Ak je nový uhol rovný starému, tak ihneď ukončíme funkciu. Je dôležité vyslať signál angleChanged() len v prípade, že sa uhol skutočne zmení.

Potom nastavíme novú hodnotu uhla a prekreslíme widget. Funkcia repaint() vymaže widget (t.j. vyplní ho jeho farbou pozadia) a vyšle widgetu udalosť paint event. To okamžite zavolá funkciu obsluhy udalosti paint event daného widgetu. Ak chcete, aby Qt vyslalo udalosť neskôr (keď preberie riadenie), použite funkciu update().

Nakoniec vyšleme signál angleChanged(), ktorý oznámi okolitému svetu, že sa uhol zmenil. Kľúčové slovo emit je unikátne pre Qt a nie je to regulárna syntax C++. V skutočnosti je to makro.

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

Toto je náš prvý pokus o napísanie funkcie obsluhujúcej udalosť paint event. Argumenty udalosti obsahujú popis udalosti paint event. QPaintEvent obsahuje obdĺžnikovú oblasť vo widgete, ktorá musí byť upravená. Pre tentokrát budeme leniví a budeme vždy prekresľovať celý widget.

Náš kód zobrazí hodnotu uhla na pevnej pozícii vo widgete. Najprv vytvoríme objekt QString. QString je Qt reťazcová trieda (detaily viď dokumentácia). Potom nastavíme reťazec použijúc funkciu QString::sprintf(), ktorá je podobná štandardnej C funkcii sprintf(). Nakoniec nakreslíme text na pozícii 200,100 widgetu (relatívne k základnej čiare textu) pomocou funkcie QWidget::drawText(). Obvykle budete používať QPainter na kreslenie widgetu, ale drawText() je dostatočná funkcia na vykreslenie textu. V nasledujúcej kapitole uvidíte ako pracuje QPainter.

main.cpp

    #include "cannon.h"

Vložíme definíciu novej triedy.

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

Tentokrát do nášho hlavného widgetu vložímejeden objekt LCDRange a jeden CannonField.

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

V konštruktore nastavíme rozsah nášho LCDRange na 5..70, umiestnime ho 10 pixelov pod tlačidlo quit a nastavíme jeho veľkosť na 75x130 pixelov.

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

Vytvoríme a nastavíme náš CannonField. Umiestnime ho 10 pixelov vpravo od LCDRange a na tú istú pozíciu y ako LCDRange. Veľkosť bude nastavená udalosťou resize event.

Potom nastavíme farbu pozadia widgetu CannonField. QColor je jedna z tried farieb knižnice Qt. Tu natvrdo nastavíme RGB hodnoty na červená=250, zelená=250 a modrá=200 (čo nám dá pastelovú žltú farbu). Rozsah hodnôt RGB je 0..255. QColor môžete nastaviť aj pomocou HSV farebného modelu.

Iná farebná trieda, QPalette, poskytuje celé pole farieb, takže možete meniť farby bez poškodenia 3D efektov.

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

Tu pripájame signál valueChanged() z LCDRange na slot setAngle() objektu CannonField. Tým sa zmení hodnota uhla v CannonField vždy, keď užívateľ pohne s LCDRange. Robíme tu aj opačné pripojenie, takže zmena uhla v CannonField upraví hodnotu v LCDRange. V tomto príklade nikdy nemeníme uhol v CannonField priamo, ale vďaka druhému connect() si zaistíme, že žiadne budúce zmeny nepoškodia synchronizáciu medzi týmito dvoma hodnotami.

Toto ilustruje silu komponentového programovania a správneho zapuzdrenia.

Všimnite si, aké dôležité je vysielať signál angleChanged() len ak sa uhol skutočne zmenil. Ak by aj LCDRange aj CannonField mali v tomto chybu, program by sa zacyklil po prvej zmene jednej z týchto hodnôt.

        angle->setValue( 60 );

Nakoniec nastavíme počiatočnú hodnotu uhla. Všimnite si, že to spustí spojenie medzi LCDRange a CannonField.

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

Dáme CannonField všetok priestor, ktorý nám ostáva, okrem 10 pixelového okraja zdola a sprava.

Správanie

Keď pohnete rolovacou lištou, CannonField zobrazí novú hodnotu uhla. Pri zmene veľkosti CannonField poskytne toľko miesta, koľko sa dá.

Na počítačoch s Windows a 256 farebným displejom bude nová farba pozadia ditherovaná (poskladaná z bodiek základných farieb). Ďalšia kapitola to obchádza.

Cvičenia

Urobte pozíciu vypisovaného textu závislú na hodnote uhla.

Zmente obsluhu udalosti zmeny veľkosti tak, aby dávala maximum miesta pre LCDRange namiesto CannonField.

Teraz môžete ísť na kapitolu deväť.

[Predchádzjúci tutoriál] [Ďalší tutoriál] [Hlavná stránka tutoriálu]


Copyright © 1998 Troll TechTrademarks
Qt version 1.42