Signaalit ja slotit

Tällä kierroksella käytämme Colorpicker-ohjelmaa sekä esimerkkinä että harjoituksen koodipohjana. Tässä osiossa tutkimme sen käyttölittymäkomponentteja ja toiminnallisuutta. Kopioi projekti hakemistosta template/12/colorpicker_designer hakemistoon student/12/colorpicker_designer, ja suorita sen sisältämä ohjelma.

Alla olevassa kuvassa näkyy Colorpicker-koodipohjan käyttöliittymä

../../_images/colorpicker_template.png

ja lopullisen ohjelman käyttöliittymä sen jälkeen, kun olet täydentänyt koodipohjan eli suorittanut seuraavan Plussa-osion harjoituksen.

../../_images/colorpicker_final.png

Colorpicker-ohjelman lopullisessa käyttöliittymässä on kolme liukusäädintä (slider) ja kolme valintalaatikkoa (spinbox), joiden avulla voidaan valita värin RGB-arvon värikomponentit, eli punaisen värin määrä, vihreän värin määrä ja sinisen värin määrä. Lisäksi näytöllä on etiketti (label), jossa käyttäjälle näytetään valittua väriä, joka käyttöliittymäkomponenteissa valituista arvoista kullakin hetkellä muodostuu. Kokeile ohjelman toimintaa vaihtamalla värikomponenttien arvoja ja seuraa, miten näytöllä esitetty väri muuttuu.

Huomaat, että värilaatikko (label) on sidottu kaikkien liukusäädinten arvoihin, koska sen väri muuttuu minkä tahansa em. komponentin arvon muuttuessa.

Signal & slot -mekanismi

Qt:ssa kaikki komponentit on toteutettu siten, että ne on periytetty QWidget-luokasta, kuten myös edellisessä materiaaliosiossa mainittu QMainWindow.

../../_images/widgetit_kaavio.png

Edellä kuvattu käyttöliittymäkomponenttien liittäminen toisiinsa tapahtuu nk. signal-slot-mekanismilla. Yksinkertaistetusti sanottuna ideana on, että kaikki QObject-luokasta perityt luokat voivat lähettää signaaleja ja vastaanottaa niitä slotien avulla. Signaalin lähetys tapahtuu avainsanan emit avulla esim. liukusäätimellä voisi olla

emit valueChanged(value_);

tai

emit valueChanged(42);
../../_images/coin-in-slot-svgrepo-com.png

Emme ala suomentaa sanaa slot tässä tekstissä. Sitä voi ajatella kolikkoautomaatin kolona, johon kolikon laittamalla saa aktivoitua toiminnon (vrt. rahapelin käynnistys, jukeboxin musiikki tms).

Kun katsomme ohjelmakoodin tasolla, signaali näyttää funktiolta. Voit halutessasi ajatella signaalin lähettämistä funktiokutsuna. Erona tavalliseen funktiokutsuun on se, että signaalin lähettävä komponentti ei tiedä, kuka/mikä signaalin ottaa vastaan. Lisäksi vastaanottajia voi olla useampia.

../../_images/box.png

Colorpicker-ohjelmassa liukusäätimen arvon muuttaminen aiheuttaa valueChanged-signaalin lähetyksen. Qt toimii siten, että signaalin lähetys aiheuttaa siihen sidottujen slotien aktivoitumisen. Lopullisessa Colorpicker-ohjelmassa liukusäätimen lähettämä valueChanged-signaali aiheuttaa sen “parina” olevan valintalaatikon setValue-slotin aktivoitumisen, joka johtaa valintalaatikon arvon muuttumiseen. Valintalaatikon arvon muuttuminen aiheuttaa vastaavalla tavalla sekä sen “parina” olevan liukusäätimen arvon muuttumisen että värilabelin värin muuttumisen.

Kuvana nämä suhteet voisi esittää vaikkapa tähän tyyliin:

../../_images/colorpicker_connections.png

Tämä oli hyvin yksinkertaistettu esitys Qt:n signaalien toiminnasta. Halutessasi perehtyä aiheeseen paremmin, suosittelemme lukemaan Qt:n dokumentaatiota signals & slots -mekanismista. Qt:n dokumentaatiossa asiat on selitetty yleisellä tasolla, toisin kuin kurssimateriaalissa, jossa käytetään esimerkkinä yhtä ohjelmaa.

Colorpickerin ohjelmakoodi

Tutkitaan Colorpickerin ohjelmakoodia. Tiedostossa main.cpp ei ole mitään eroa edellisessä materiaaliosiossa tutkimaamme tyhjään ikkunaan verrattuna, joten aloitamme tiedostosta MainWindow.hh.

Kuten edellisessä osiossakin, tässäkin luokka MainWindow on periytetty luokasta QMainWindow ja rajapinnassa lukee ensimmäisenä Q_OBJECT. Samoin luokan julkisessa rajapinnassa on rakentajan ja purkajan määrittelyt.

Osasta private slots löytyvät ne slotit, jotka pääikkunassa on toteutettuna luokan omaan käyttöön, eli vain luokka itse voi sitoa signaaleja niihin. Jos lisäksi luokalla olisi osa public slots, sisältäisi se signaaleja, joita voisi käyttää myös luokan ulkopuolelta. Slot-osien näkyvyydet ovat siis yhdenmukaisia aiemmin oppimiemme luokkarajapintojen kanssa.

Luokalla MainWindow on toteutettuna yksi slot, onColorChanged, jossa kerrotaan, miten ikkunan on tarkoitus toimia, kun käyttäjä muuttaa värin arvoa.

Luokan private-osassa on määriteltynä jäsenmuuttujat aivan kuten muillakin luokilla. Kuten edellisellä ohjelmointikurssilla opimme, käyttöliittymäluokan jäsenmuuttujina voi olla sen sisältämiä käyttöliittymäkomponentteja. Jäsenmuuttujiksi kannattaa tallettaa vain ne käyttöliittymäkomponentit, joita meidän pitää pystyä käsittelemään ohjelmakoodissa. Tästä syystä luokalla ei ole yhtään widgettiä attribuuttinaan. Tässä esimerkissä kaikki widgetit on määritelty Qt Designerin kautta, jota opit käyttämään seuraavassa harjoitusosiossa. Ohjelmakoodissa näihin widgeteihin voidaan viitata käyttöliittymän (ui) kautta.

Luokkarajapinnassa on määriteltynä myös vakio RGB-arvojen maksimiarvolle.

Seuraavaksi avataan tiedosto MainWindow.cpp. Rakentajan alustuslistassa alustetaan kaikki jäsenmuuttujina olevat käyttöliittymäkomponentit, kuten olemme jäsenmuuttujille tottuneet tekemään. Rakentajan rungossa tehdään käyttöliittymäkomponenteille vielä joitain asetuksia, joita niiden rakentajassa ei suoraan voi tehdä (setMinimum ja setMaximum).

Tärkeimpänä kohtana rakentajan rungon lopussa nähdään, miten jokaisen liukusäätimen muutossignaali yhdistetään MainWindow-olion slotiin onColorChanged, jonka tarkoitus jo edellä mainittiinkin. Harjoitustehtäväksi on jätetty liukusäätimien ja valintalaatikoiden muutossignaalien yhdistäminen toisiinsa pareittain.

Signaalien ja slotien lopullinen tilanne on esitetty edellisessä kuvassa. Vertaile koodia ja kuvaa toisiinsa ja yritä selvittää, mitkä kytkennät on jo tehty ja mitkä on jätetty harjoitustehtäväksi.

Rakentajan lopussa kutsutaan slotia onColorChanged() tavallisen funktion tavoin, jotta värilabelin väri muuttuu värivalitsimien alkuarvojen mukaiseksi.

Slotin onColorChanged toteutus on lyhyt. Siinä luodaan QColor-tyyppinen olio, jonka voi näyttää QLabel-oliossa. Luotavan olion RGB-arvo, joka välitetään rakentajalle, saadaan haettua liukusäädinolioilta.

Lopuksi on hyvä pysähtyä miettimään, miksi onColorChanged-slot on toteutettu MainWindow-luokkaan. Tarkemmin ajateltuna se on colorLabel-olio, joka muuttaa väriään. Slotissa pitää päästä käsittelemään liukusäädinolioita, joten se on helpointa toteuttaa pääikkunassa. Liukusäädinoliot ovat pääikkunan lapsia, eli siksi ne ovat käsiteltävissä pääikkunassa. (Liukusäädinolioita tai viitteitä niihin olisi toki mahdollista välittää parametreina luokan ulkopuolellekin, mutta ei pohdita sitä ratkaisua tässä.)

Lopuksi

Käyttääkseen Qt:ta täytyy ohjelmoijan ymmärtää hyvin paljon C++-ohjelmointikielen ominaisuuksia, koska kaikki käyttöliittymäkomponentit on toteutettu periytettyinä luokkina, ja niistä luodut oliot talletetaan osoittimien avulla. Nyt ymmärrämme, miksi tätä opintojaksoa ei aloitettu suoraan graafisista käyttöliittymistä, vaikka se olisikin tuntunut kivalta edellisen ohjelmointikurssin päätyttyä graafisten käyttöliittymien alkeisiin.