Home

Klasy - Adnotacje - Drzewo - Funkcje - Powrót - Struktura

Rozdział 10: Gładko jak po maśle

Screenshot of tutorial ten

W tym przykładzie wprowadzimy rysowanie w pixmapach aby zapobiec migotaniu. dodamy także kontrolę siły.

Analiza kodu linia po linii

cannon.h

Pole CannonField ma teraz wartość siły w kombinacji z kątem.
        int   angle() const { return ang; }
        int   force() const { return f; }
    
    public slots:
        void  setAngle( int degrees );
        void  setForce( int newton );
    
    signals:
        void  angleChanged( int );
        void  forceChanged( int );
Interfejs dla słiły posiada tą samą precuzję co dla kąta.
    private:
        QRect cannonRect() const;
Musimy umieścić definicję prostokąta zawierającego działo w osobnej funkcji.
        int ang;
        int f;
    };
Siła jest przechowywana w rzeczywistej liczbie f.

cannon.cpp

    #include <qpixmap.h>
Dołączamy definicję klasy QPixmap.
    CannonField::CannonField( QWidget *parent, const char *name )
            : QWidget( parent, name )
    {
        ang = 45;
        f = 0;
        setPalette( QPalette( QColor( 250, 250, 200) ) );
    }
Siła (f) jest inicjalizowana przez zero.
    void CannonField::setAngle( int degrees )
    {
        if ( degrees < 5 )
            degrees = 5;
        if ( degrees > 70 )
            degrees = 70;
        if ( ang == degrees )
            return;
        ang = degrees;
        repaint( cannonRect(), FALSE );
        emit angleChanged( ang );
    }
Dokonaliśmy małych zmian w funkcji setAngle(). Przemalowuje tylko część widgetu, który zawiera działo. Argument FALSE wskazuje, że prostokąt nie powinien być wymazywany zanim do widgetu zostanie wysłane wydarzenie rysowania. To przyśpiesza i upłynnia malowanie.
    void CannonField::setForce( int newton )
    {
        if ( newton < 0 )
            newton = 0;
        if ( f == newton )
            return;
        f = newton;
        emit forceChanged( f );
    }
Implementacja setForce() jest dość podobna do setAngle(). Jedyną różnicą jest to, że skoro nie pokazujemy wartości siły, to nie musimy przemalowywać widgetu.
    void CannonField::paintEvent( QPaintEvent *e )
    {
        if ( !e->rect().intersects( cannonRect() ) )
            return;
Zoptymalizowaliśmy wydarzenie rysowania tak, aby przerysowywać te części który wymagają uaktualnienia. Najpierw więc sprawdzamy czy musimy coś przesysowywać, jeżeli nie to wracamy.
        QRect cr = cannonRect();
        QPixmap pix( cr.size() );
Teraz utworzymy tymczasową pixmapę, której użyjemy do rysowania bez migotania. Wszystkie operacje rysownicze będą teraz wykonywane na tej pixmapie, a ona bedzie rysowana na ekranie pojedynczą operacją.

Jest to esencja rysowania be zmigotania: Narysuj każdy piksel dokładnie jeden raz. Mniej i dostaniesz błędy rysowania. Więcej i bedziesz miał migotanie.

        pix.fill( this, cr.topLeft() );
Wypełniamy pixmapę tłem z tego widgetu.
        QPainter p( &pix );
        p.setBrush( blue );
        p.setPen( NoPen );
        p.translate( 0, pix.height() - 1 );
        p.drawPie( QRect( -35,-35, 70, 70 ), 0, 90*16 );
        p.rotate( -ang );
        p.drawRect( QRect(33, -4, 15, 8) );
        p.end();
Rysujemy, tak jak w rozdziale 9, lecz teraz rysujemy w pixmapie.

W tym punkcie, mamy zmienną rysowniczą i pixmapę, która wygląda dokładnie dobrze, ale ciągle nie rysowaliśmy na ekranie.

        p.begin( this );
        p.drawPixmap( cr.topLeft(), pix );
Tak więc otwieramy rysownika na CannonField itself i rysujemy pixmapę.

To wszystko !

    QRect CannonField::cannonRect() const
    {
        QRect r( 0, 0, 50, 50 );
        r.moveBottomLeft( rect().bottomLeft() );
        return r;
    }
Tak funkcja zwraca prostokątne granice zawierające działo w koordynatach widgetów. Najpierw tworzymy Kwadrat 50x50, potem je przesuwamy, tak że lewy dolny brzeg równa się lewemu dolnemu brzegowi widgetu.

Funkcja QWidget::rect() zwraca brzegi prostokąta widge w własnych koordynatach widgetu (gdzie górny lewy róg to 0,0).

main.cpp

    MyWidget::MyWidget( QWidget *parent, const char *name )
            : QWidget( parent, name )
    {
Konstruktor jest praktycznie ten sam, lecz dodaliśmy kilka linii.
        LCDRange *force  = new LCDRange( this, "force" );
        force->setRange( 10, 50 );
Dodamy drugi LCDRange, który będzie używany dla siły.
        connect( force, SIGNAL(valueChanged(int)),
                 cannonField, SLOT(setForce(int)) );
        connect( cannonField, SIGNAL(forceChanged(int)),
                 force, SLOT(setValue(int)) );
Łączymy widget force z widgetem cannonField zupełnie tak jak to robiliśmy z widgetem angle.
        QVBoxLayout *leftBox = new QVBoxLayout;
        grid->addLayout( leftBox, 1, 0 );
        leftBox->addWidget( angle );
        leftBox->addWidget( force );
W rozdziale 9, umiesciliśmy angle w dolnej lewej komórce. Teraz chcemy umieścić dwa widgety w tej komórce, więc tworzymy pionowy prostokąt, w umieszczamy angle i range w pionowym prostokącie.
        force->setValue( 25 );
Inicjalizujemy wartość siły przez 25.

Zachowanie

Migotanie odeszło w przeszłość i mamy już kontrolę nad siłą.

Excercises

Spróbuj uzależnić wielkość lufy od siły.

Umieść działo w prawym dolnym rogu.

Dodaj lepszą obsługę klawiatury. Na przykład niech + i - zwiększają siłę a enter to strzał. Podpowiedź: QAccel i nowe sloty addStep() i subtractStep() w LCDRange, tak jak w QSlider::addStep().

Możesz teraz przejść do rozdziału jedenastego.

[Poprzedni tutorial] [Następny tutorial] [Główna strona tutoriala]



 
Copyright (c) 2000 Troll Tech Znaki towarowe
Wersja Qt 2.1.0