Home

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

Rozdział 7: Jedna rzecz prowadzi do drugiej

Screenshot of tutorial seven

Ten przykład pokazuje ja tworzyć własne widgety z sygnałami i slotami, oraz jak połączyć je razem w bardzo skomplikowane sposoby. Po raz pierwszy podzielimy źródło na kilka plików.

Analiza kodu linia po linii

lcdrange.h

W dużej części plik ten pochodzi z main.cpp w rozdziale szóstym a jedyne zmiany odnotowane zostały poniżej.
    #ifndef LCDRANGE_H
    #define LCDRANGE_H
Jest to klasyczna dla C konstrukcja dzięki której unika się błędów gdy plik nagłówkowy zostanie zawarty więcej niż raz. Jeśli nie używasz jej jeszcze: czas zacząć, to dobry zwyczaj. Dyrektywa #ifndef powinna zawierać wszystkie pliki nagłówkowe.
    #include <qvbox.h>
qvbox.h zostaje dołączone. LCDRange dziedziczy QVBox, a plik nagłówkowy klasy rodzica musi być zawsze zawarty. Troszeczkę oszukiwaliśmy w poprzednich rozdziałach, i pozwaliśmy aby qwidget.h był włączany pośrednio poprzez inne pliki nagłówkowe jak qpushbutton.h.
    class QSlider;
Jest to jeszcze jeden klasyczny trik, lecz jest znacznie rzadziej używany. Ponieważ nie potrzebujemy QSlider w interfejsie klasy, a tylko w implementacji, używamy poprzedającej deklaracji klasy w pliku nagłówkowym, oraz #include pliku nagłówkowego QSlider w pliku .cpp.

Czyni to kompilację dużych projektów znacznie szybszą, ponieważ kiedy zmienia się plik nagłówkowy mniej plików musi zostac przekompilowanych. Może to nawet dwukrotnie zwiększyć szybkość kompilacji.

    class LCDRange : public QVBox
    {
        Q_OBJECT
    public:
        LCDRange( QWidget *parent=0, const char *name=0 );
Zwróć uwagę na Q_OBJECT. To makro musi być zawarte we wszystkich klasach, które zawierają sygnały i/lub sloty. Jeśli jesteś ciekawy definjuje ono impementację funkci z pliku meta objektów.
        int value() const;
    public slots:
        void setValue( int );
    
    signals:
        void valueChanged( int );
Tych trzech członków tworzą interfejs pomiędzy tym widgetem a innymi komponentami programu. Do tej pory LCDRange tak naprawdę nie posiadał interfejsu.

value() jest funckją publiczną umożliwiającą dostęp do wartości LCDRange. setValue() jest naszym pierwszym własnym slotem, a valueChanged() jest naszym pierwszym własnym sygnałem.

Slots muszą być zaimplementowane w normalny sposób (pamiętaj, slot to także członek funkcji C++). Sygnały są automatycznie implementowane w plikumeta objektów. Signały spełniają prawa dostępu chronioncyh funkcji C++ , tzn. mogą być emitowane jedynie przez klasę w której są zdefiniowane lub przez klasę dziedziczącą z nich.

Sygnał valueChanged() zostaje użyty gdy wartość LCDRange ulegnie zmianie - tak ja to odgadłeś z jego nazwy. I to nie jest ostatni sygnał kóry nazywa się w stylu cośChanged().

lcdrange.cpp

Ten plik pochodzi głównie z t6/main.cpp a jedyne zmiany widzisz poniżej.
        connect( slider, SIGNAL(valueChanged(int)),
                 lcd, SLOT(display(int)) );
        connect( slider, SIGNAL(valueChanged(int)),
                 SIGNAL(valueChanged(int)) );
To jest kod konstruktora LCDRange.

Pierwsze połączenie jest takie samo jak w poprzednim rozdziale. Drugie jest nowe: Łączy sygnał suwaka valueChanged() do sygnału tego objektu valueChanged. connect() z trzema argumentami zawsze łączy do sygnałów lub slotów w tym objekcie.

Tak to prawda. Sygnały mogą być podłączane do innych sygnałów. Kiedy wymeitowany zostanie pierwszy, to i drugi sygnał zostanie wyemitowany.

Spójrzmy co się stanie kiedy użytkownik przsunie suwak: Suwak rozpoznaje, że jego wartość uległa zmianie, więc emituje sygnał valueChanged(). Ten sygnał jest podłączony zarówno do slotu display() z QLCDNumber jak i do slotu valueChanged() z LCDRange.

Tak więc, gdy emitowany jest sygnał, LCDRange emituje swój własny sygnał valueChanged(). Dodatkowo, wywoływany jest QLCDNumber::display() i pokauje nową cyfrę.

Zauważ, iż nie masz gwarancji jakiejkolwiek kolejności wykonania - LCDRange::valueChanged() może zostać wyemitowany przed lub po QLCDNumber::display(), całkowicie arbitralnie.

    int LCDRange::value() const
    {
        return slider->value();
    }
Implementacja value() jest bardzo prosta, po prostu zwraca wartość suwaka.
    void LCDRange::setValue( int value )
    {
        slider->setValue( value );
    }
Implementacja setValue() jest równie prosta. Zauważ, iż skoro suwak i wyświetlacz LCD są połaczone, ustawienie wartości suwaka automatycznie uaktualni wskazanie wyświetlaczaLCD number. Dodatkowo, suwak automatycznie ustawi swoją wartość gdyby znalazła się poza przewidzianym zakresem.

main.cpp

        LCDRange *previous = 0;
        for( int r = 0 ; r < 4 ; r++ ) {
            for( int c = 0 ; c < 4 ; c++ ) {
                LCDRange* lr = new LCDRange( grid );
                if ( previous )
                    connect( lr, SIGNAL(valueChanged(int)),
                             previous, SLOT(setValue(int)) );
                previous = lr;
            }
        }
Całość main.cpp została skopiowana z poprzedniego rozdziału, za wyjątkiem konstruktora dla MyWidget, gdzie tworzyliśmy 16 objektów LCDRange teraz łaczymy je razem przy użyciu mechanizmowi sygnałów/slotów. Każdy ma swój sygnał valueChanged() podłączony do slotu setValue() w poprzednim. Ponieważ LCDRange emituje sygnał valueChanged() kiedy jego wartość zmienia się (niespodzianka!), tworzymu tu łańcuch "chain" sygnałów i slotów.

Zachowanie

Na starcie program zachowuje się identycznie jak poprzedni. Spróbuj przesunąć suwak w na prawym dole...

Ćwiczenia

Użyj dolnego suwaka by ustawić wszystkie wyświetlacze LCD na 50. Potem ustaw górną połówkę na 40 przez kliknięcie raz na lewym uchwycie suwaka. Teraz użyj tego po lewej aby ustawić pozostałe siedem z powrotem na 50.

Kliknij w lewą część suwaka na dolnym prawym. Co się dzieje ? Dlaczego jest to poprawne zachowanie ?

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

[Poprzedni tutorial] [Następny tutorial] [Głowna strona tutoriala]



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