W tym przykładzie wprowadzimy pierwszy własny widget, który potrafi się rysować. Dodamy także użyteczny interfejs klawiatury (tylko z dwoma liniami kodu).
void setRange( int minVal, int maxVal );Dodajemy teraz możliwiość ustawienia zakresu LCDRange. Dotychczas był ustawiony sztywno na 0..99.
void LCDRange::setRange( int minVal, int maxVal ) { if ( minVal < 0 || maxVal > 99 || minVal > maxVal ) { qWarning( "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 ); }setRange() ustawia zakres suwaka w LCDRange. Ponieważ musimy ustawić QLCDNumber tak, aby zawsze pokazywał dwie cyfry, musimy ograniczyć możliwy zakres minVal i maxVal do 0..99 tak aby uniemożliwić przepełnienie się QLCDNumber. (Mogliśmy ograniczyć z dołu do -9 lecz nie ma potrzeby tego czynić.) Jeżeli arguenty będą niewłaściwe, użyjemy funkcji Qt qWarning(), aby ostrzec natychmiast użytkownika. qWarning() jest funkcją podobną do printf i wyświetla na stderr. Możesz zainstalowac własną funkcję przy użyciu ::qInstallMsgHandler() jeżeli chcesz.
class CannonField : public QWidget { Q_OBJECT public: CannonField( QWidget *parent=0, const char *name=0 );CannonField dziedziczy po QWidget, użyjemy tego samego idiomu jak dla LCDRange.
int angle() const { return ang; } QSizePolicy sizePolicy() const; public slots: void setAngle( int degrees ); signals: void angleChanged( int );Obecnie, CannonField zawiekra tylko wartość kąta, dla którego udostępnimy interfejs przy użyciu tego samego idiomu jak dla LCDRange.
protected: void paintEvent( QPaintEvent * );To jest drugi z uchwytów wydarzeń w QWidget jaki spotkaliśmy. Ta wirtualna funkcja wywoływana jest przez Qt jeśli tylko widget musi się uaktualnić (np.: narysować swoją powierzchnię).
CannonField::CannonField( QWidget *parent, const char *name ) : QWidget( parent, name ) {Znowu użyjemy tego samego idiomu jak dla LCDRange w poprzednim rozdziale.
ang = 45; setPalette( QPalette( QColor( 250, 250, 200) ) ); }KOnstruktor inicjalizuje wartość kąta na 45 stopni, oraz ustawia własną paletę dla tego widgetu.
Ta paleta używa wskazanego koloru jako tła i wybiera odpowednio inne kolory. (W tym widgecie użyjemy jedynie koloru tła i tekstu.)
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 ); }Ta funkcja ustawia wartość kąta. Wybraliśmy zakres 5..70 i ustawiliśmy odpowiednio liczbe stopni. Nie bedziemy generować ostrzeżenia gdy nowa wartość kąta będzie poza zasięgiem.
Jeżeli nowy kąt równa się staremu, natychmiast powracamy. Ważne jest by emitować jedynie sygnał angleChanged() kiedy kąt naprawdę się zmienił.
Potem ustawimy nową wartość kąta oraz przerysujemy widget. Funkcja QWidget::repaint() czyści widget (zwykle wypełnia go kolorem tła) i wysyła wydarzenie narysowania do widgetu. W rezultacie daje to wywołanie funkcji wydarzenia rysowania widgetu.
W końcu, emitujemy sygnał angleChanged(), aby powiedzieć zewnętrznemu światu, że kąt uległ zmianie. Słowo kluczowe emit jest unikalne dla Qt i nie należy do składni C++. W rzeczywistości jest to makro.
void CannonField::paintEvent( QPaintEvent * ) { QString s = "Angle = " + QString::number( ang ); QPainter p( this ); p.drawText( 200, 200, s ); }To jest nasza pierwsza próba zapisania wydarzenia uchwytu rysowania widgetu. Argument wydarzenia zawiera opis wydarzenia rysowania. QPaintEvent zawiera region, który musi być uaktualniony w widgecie. Teraz jednak, będziemy leniwi i przerysujemy po prostu wszystko.
Nasz kod wyświetla wartość kąta w stałej pozycji. Najpierw tworzymy QString z tekstem i kątem, potem tworzymy QPainter operującego na tym widgecie, i używamy go do namalowania ciągu. Wrócimy do QPainter później; potrafi robić wiele wspaniałych rzeczy.
#include "cannon.h"Zawieramy naszą nową klasę:
class MyWidget: public QWidget { public: MyWidget( QWidget *parent=0, const char *name=0 ); };Tym razem, zawieramy pojedynczy LCDRange i CannonField w naszym głównym widgecie.
LCDRange *angle = new LCDRange( this, "angle" );W konstruktorze tworzymy i ustawiamy nasz nowy LCDRange.
angle->setRange( 5, 70 );Ustawiamy LCDRange tak, aby zaakceptował wartości za zakresu od 5 do 70 stopni.
CannonField *cannonField = new CannonField( this, "cannonField" );Tworzymy nasz CannonField.
connect( angle, SIGNAL(valueChanged(int)), cannonField, SLOT(setAngle(int)) ); connect( cannonField, SIGNAL(angleChanged(int)), angle, SLOT(setValue(int)) );Tutaj łączymy sygnał valueChanged() z LCDRange do slotu setAngle() z CannonField. To uaktualni wartość kąta CannonField kiedykolwiek użytkownik przestawi LCDRange. Tworzymy także połączenie zwrotne, tak że przez zmianę kąta w CannonField nastąpi uaktualnienie wartości LCDRange. W naszym przykładzie, niegdy nie zmieniamy bezpośrednio wartości CannonField, lecz przez wykonanie ostatniego connect(), zapewniamy, iż w przyszłości żadne zmiany nie pospsują synchronizacji pomiędzy tymi dwoma wartościami.
Pokazuje to siłę programowania komponentowego oraz właściwe kapsułkowanie.
Zauważ jak ważne jest fakt, iż emitowany jest tylko sygnał angleChanged() kiedy kąt rzeczywiście się zmienia. Jeżeli zarówno LCDRange jak i CannonField omijały by sprawdzenie tego, program wpadł by w nieskończoną pętlę po pierwszej zmianie tej wartości.
QGridLayout *grid = new QGridLayout( this, 2, 2, 10 ); //2x2, 10 pixel borderDotychczas używaliśmy widgetów nie wymagających składania: QVBox i QGrid do zarządzania geometrią. Jednakże teraz chcemy mieć trochę więcej kontroli nad rozmieszczeniem, więc przełączamy się na silniejszą klasę QGridLayout. QGridLayout nie jest widgetem a inną klasą która może zarządzać dziećmi jakiegokolwiek widgetu.
Jak wskazuje komentarz, tworzymy tablice dwa na dwa z dziesęciopikslową ramką. (Konstruktor QGridLayout może być dość tajemniczy,dobrze jest więc umieścić komentarze dego typu.)
grid->addWidget( quit, 0, 0 );Dodajemy przycisk Quit w górnej lewej komórce siatki: 0, 0.
grid->addWidget( angle, 1, 0, Qt::AlignTop );Umieszczamy kąt LCDRange w dolnej lewej komórce, ustawiony na górę swojej komórki. (Takie właśnie ustawienie można uzyskać z QGridLayout, a QGrid na to nie pozwala.)
grid->addWidget( cannonField, 1, 1 );Umieszczamy objekt CannonField i dolnej prawej komórce. (Tak, górna komórka jest pusta.)
grid->setColStretch( 1, 10 );Powiemy QGridLayout, iż prawa kolumna (kolumna 1) może się rozszerzać. Ponieważ lewa tego nie robi (współczynnik rozszerzalności na 0 - wartość domyślna), QGridLayout będzie próbować pozwolić rozmiarom lewych wodgetów na pozostanie bez zmian, a rozszerzać jedynie CannonField jedy MyWidget będzie rozszerzany.
angle->setValue( 60 );Ustawiamy wartość kąta. Zauważ, że uruchomi to połączenie to LCDRange z CannonField.
angle->setFocus();Naszym końcowym zadaniem jest ustawienie angle na klawiaturę, tak że domyślnie klawiatura sterowac będzie widgetem LCDRange.
LCDRange nie zawiera żadnego wydarzenia keyPressEvent(), więc nie będzie to strasznie przydatne. Jednak konstruktor dostał tylko jedną nową linię:
setFocusProxy( slider );LCDRange ustawia suwak na swoje włane aktywne proxy. Oznacza to, że kiedy ktoś (program lub użytkownik) chce przyporzadkować klawiaturę do LCDRange, suwak powinien się tym zająć.
Kiedy suwak jest przesuwany, CannonField wyświetla nową wartość kąta.
Na maszynach Windows z 8-bitowym wyświetlaczem, nowe tło ma kolor śmierci. Zajmiemy się tym w następnym rozdziale.
Pozostaw wywołanie setFocus(). Które zachwowanie preferujesz ?
Zmień "Quit" na "&Quit" w wywołaniu QButton::setText(). Jak zmienił się wygląd przycisku ? Co się stanie jak wciśniesz Alt-Q ?
Wycentruj tekst w CannonField.
Możesz teraz przejść do rozdziału dziewiątego.
[Poprzedni tutorial] [Następny tutorial] [Głowna strona tutoriala]
Copyright (c) 2000 Troll Tech | Znaki towarowe |
Wersja Qt 2.1.0
|