A múltkor írásomban bemutattam, hogy a PHP – bár elsődleges arra lett kifejleszve – nem csak webes nyelvként használható. Ebben a leírásban még inkább közelítünk az asztali alkalmazások világához és egy működőképes program alapján megismerkedhetünk a PHP-GTK alapvető működésével.
Az ismerkedés alatt ebben a leírásban kevésbé a gyakorlatias bemutatást értem, helyette egy átfogóbb képet szeretnék adni a GUI-val rendelkező programok készítéséről, működéséről. A fentebb linkelt íráshoz hasonlóan itt is három részre osztható a leírás: a futtató környezet előállítása, egy alkalmazás bemutatása, majd az alkalmazáson keresztül a fontosabb dolgok leírása. Ellenben itt már – főként a phpdoc kommentek miatt – nagyságrenddel hosszabb kódról van szó, így azt a cikkben nem közlöm (csak idézek belőle), hanem letölthetővé teszem.
Azonban először a futtató környezetet kellene előállítani. Hogy jobban megértsük a működését, először is tisztázzuk, mi az a PHP-GTK! A GTK (Gimp ToolKit) eredetileg a GIMP képszerkesztő programhoz készült, azonban később attól elkülőnített, grafikus felületek lértehozására készített eszközkészlet. A két legnagyobb előnye, hogy több platformmal (Linux, Unix, Window, …) és több programozási nyelvvel (eredetileg C-ben íródott, de elérhető C++-hoz, Perl-hez, Ruby-hoz stb-hez is) is használható. A PHP-GTK ezen eszközkészletet adja a PHP programozó kezébe a php_gtk modullal.
A Parancssori programozás PHP-vel cikkben ismertetett módon a PHP-GTK futtatót is felvehetjük a Path-be, és indíthatjuk a php fájlnév.php mintára. A szükséges csomagokat letölthetjük a PHP-GTK hivatalos weboldaláról: tömörített állományként Windowsra előre fordított binárisok formájában. Érdemes letölteni mind a Windows binary with GTK+ 2.10 és Windows binary extension pack csomagokat is, majd közös könyvtárba kitömöríteni őket.
Az ext mappába kerülnek a modulok (extension), amelyeket a php-cli.ini fájlba beszúrt extension=kiterjesztes.dll sorral tölthetünk be. A cikkben bemutatott programhoz szükség lesz az imap modulra, így írjuk be az extension=php_imap.dll -t a fenti fájlba (lehetőleg a többi extension= sor közelébe, hogy egy helyen legyenek). Azonban, ha csak tehetjük, felesleges modulokat ne töltsünk be, feleslegesen fogyasztják a memóriát.
Lépjünk be abba a mappába, amelybe kitömörítettük a PHP-GTK fájljait, majd a következő kódot mentsük le teszt.php néven!
<?php $msgbox = new GtkMessageDialog(null, 0, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, "Megy a PHP-GTK!"); $reply = $msgbox->run(); $msgbox->destroy(); ?> |
Ezután adjuk ki a php-win teszt.php utasítást, majd ha mindent jól csináltunk egy ablakot kell látnunk egy OK gombbal, egy ikonnal és egy „Megy a PHP-GTK!” felirattal. Egyelőre a kóddal ne foglalkozzunk, azt majd később nézzük át.
Ha minden igaz, már van egy használható PHP-GTK2 telepítésünk, úgyhogy most nézzük meg a példaprogramot. Bemutató-jellegű leírásoknál nem szeretek használhatatlan példákat mutatni (tipikusan ilyenek a Hello world példák), inkább hasznos kódokon keresztül magyarázok. A mostani példaprogram sem kivétel ez alól (hasznosságát mindenki döntse el maga), de azoknak, akik több postafiókot (és mail klienst) használnak, jól jöhet. Én például nem szeretem, ha a tálcám tele van olyan programokkal, amelyeket nem használok, így a mail kliens(eke)t elsőként zárom be, és ha épp elfelejtem napokig nem nézem a leveleim.
A példaprogram alapvetően „csöndben” (bármilyen megjelenített grafikus elem nélkül) működik, beálítható időközönként megnézi postaládáink tartalmát és egy gomb formájában értesít minket, ha bármelyikben talált egy levelet. A gombra kattintva pedig elindítja a hozzárendelt mail-klienst, majd eltüntetni azokat a gombokat, amelyekhez ugyanaz a kliens van rendelve (pl.: Nálam két fiókok a Thunderbird kezel, egy harmadikat az Outlook, G-mail-t pedig a webes felületén használom). Elsődlegesen POP3 protokollra készült, viszont megfelelően beállítva még pl. a Google címünket is képes ellenőrizni. Most még időzzünk el itt egy kicsit, és nézzük át hogyan lehet használni a programot!
Maga a program két (három) fájlt használ alapbeállításban: a mailnotifier.php-t, a mailboxes.ini-t és egy mail.png-t (ez utóbbit csak akkor, ha van: a program ikonját állíthatjuk be vele). A mailnotifier-t csak akkor kell módosítanunk, ha a nyelvet szeretnénk megváltoztatni (jelenleg angol nyelvű, angol kommentekkel – mégiscsak ez az Internet általános nyelve). Ehhez a kód elején deklarált konstansok értékét kell átírnunk. Két – opcionális – parancssori paramétere lehet, az egyik a /?, amely egy súgót jelenít meg, a másik a „-v”, amivel a horizontális elrendezés helyett függőleges elrendezést kapunk.
Postafiókjainkat a mailboxes.ini módosításával vihetjük fel, a következő formában:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ; Pop3 fiókhoz a következő formát használjuk: ;(a kommentet jelző soreleji ;-ket távolítsuk el!) ; Az Interval azt adja meg, hogy hány percenként ellenőrizzük a mail-fiókot ; Az Icon (opcionális) paraméter adja meg, hogy a gombon milyen ikon jelenjen meg (ajánlott méret: 16 * 16 pixel) ; ; Ha bármelyik érték szóközt is tartalmaz, tegyük " jelek közé! ;[Postfiók neve] ;Host=mail.szolgaltato.hu ;Port=110 ;Username=felhasznalonev ;Password=jelszo ;Interval=5 ;Icon=foobar.png ;Client="C:\Program Files\Mozilla Thunderbird\thunderbird.exe" ; Amennyiben titkosítot csatornát, esetleg más protokollt használunk, ;a Host és Port helyett használhatjuk az IMAPStr-t, amely az imap_open ;második paramétere lesz, így a pontos leírásához nézzük meg a ; http://php.net/imap_open oldalt! ; Példa: ; A Google-mail használatához engedélyeznünk kell a ;gmail webes felületén az IMAP hozzáférést! ;[Google] ;IMAPStr="{imap.gmail.com:993/ssl/imap}" ;Username=felhasznalonev@gmail.com ;Password=jelszo ;Interval=1 ;Client="http://gmail.com" ;Icon="g-mail.png" |
Ezzel a módszerrel felvihetünk akárhány postafiókot. Látható, hogy nem csak futtatható állományok adhatóak meg a kliens programnál, hanem fájlok/webcímek is; később látni fogjuk, hogy ezért buktuk a hordozhatóságot, csak Windows alatt használható a program. Nálam például 4 beállított fiókkal működik, mindhez van beállítva ikon, így ilyesmit kapot, ha három fiókomban van e-mail:
Megjegyzés: a programot beállíthatjuk automatikus indításúra, ehhez indítsuk el a regedit programot (Start -> Futatás (Run) -> regedit), majd keressük meg a HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run útvonalat a bal oldali fában és jelöljük ki. A jobb oldalon ezután jobb klikk -> Új (New) -> Karakterlánc (String), névnek írjunk be valamit (pl.: Mail Notifier), majd duplaklikkelés után értéknek a következő szöveget:
"C:\A\PHP\Futattó\Útvonala\php-win.exe" "C:\A\MailNotifier\útvonala\mailnotifier.php"
Ha felvittük a PHP-GTK-t a Path változóba, akkor elég a php-win majd a mailnotifier.php útvonala.
Amikor már eleget teszteltük (és a megtalált bugokról írtunk egy privát üzentet a fórumon BlackY-nek), akkor nézzünk bele egy kicsit a kódba. A kód hosszától (9 kilobájt) ne ijedjünk meg, több, mint fele komment (a valós kód 4 kb körül van)!
Mielőtt tovább megyünk, nézzük meg egy grafikus felület működését. Az itt leírtak természetesen nagyon messze állnak a valóságtól, ezeket a feladatokat az operációs rendszer (az ablakkezelő) látja el, viszont fontos még az elején kiemelni. Egy egyszerű példát vegyünk, „GUI”-nk alapjául: egy 28 * 7 karakteres terminál már elég hozzá. Az l alatt levő karakter jelölje a kurzort. (Valójában ez egy Text User Interface (Tui), viszont működése emlékeztet a GUI-kéhoz: a WIMP-ből (Windows, Icons, Menus, Pointers – Ablakok, ikonok, menük, mutató eszközök) az ablakot, a menüt és a pointert magában foglalja)
Hogy ebbe a „dobozba” életet leheljünk, egy hurok ismétléses vezérlési szerkezetben bizonyos időközönként lekérdezzük, történt-e változás az egér állapotában: arréb lökték-e, lenyomták-e valamelyik gombot. Ha megmozdították, akkor frissítjük a képet, a „fekete dobozt” átrakjuk a megfelelő helyre, ha lenyomták a gombot, akkor megvizsgáljuk, hogy a kurzor épp melyik „elemen” állt (ha maradunk ennél a szövegnél, akkor megnézzük, hogy hányadik karakter a fekete doboz a kint levő képben, ha ez a szám X*29 + 4 és X*29 + 13 között (X eleme {3,4,5}) van, akkor végrehajtjuk az „OK” „gomb” lenyomásához tartozó kódot, ha X*29 + 19 és X*29 + 28 között (X eleme {3,4,5}) van, akkor a Mégse gombhoz tartozóét. Egyébként pedig folytatjuk az ismétlést. Pszeudó-kódban:
1 2 3 4 5 6 7 8 9 10 11 12 13 | while( true ) { if( mouse_has_been_moved() ) { set_cursor_pos(mouse_movement_horizontal(), mouse_movement_vertical()); } if( mouse_has_been_clicked() ) { if(91 < get_cursor_pos() < 100 OR 110 < get_cursor_pos() < 119 OR 139 < get_cursor_pos() < 148) { button_ok_onclick(); } else if(106 < get_cursor_pos() < 115 OR 135 < get_cursor_pos() < 144 OR 163 < get_cursor_pos() < 172) { button_cancel_onclick(); } } } |
Ismét kiemelném, hogy ez egy nagyon leegyszerűsített példa, és a valós megoldásokhoz alig van köze. Viszont az eseményvezérelt programozás szemléltetésére jó: a második if szerkezeten belül látható, hogy amennyiben valamelyik gombra kattintottunk, meghívjuk az ahhoz rendelt eseménykezelőt. Ezt nevezzük Eseményvezérelt (event driven) programozásnak.
A weben használt kódjainknál megszokhattuk, hogy A után B utasítás hajtódik végre. Eseményvezéreltségnél nem tudhatjuk előre, hogy mi fog legközelebb történni (melyik esemény áll be), például a böngészőben nem tudhatja a kód előre, hogy a felhasználó a Refresh gombra fog kattintani, esetleg a kedvencek menüt nyitja le vagy bezárja a böngészőt. A böngésző vár, hogy a user csináljon valamit, legyen az a bezárás gombra kattintás, a refresh gomb, akármi. Ezt a várakozást egy központi függvény „végzi”, PHP-Gtk alatt ez lesz a Gtk::main() függvény.
A böngésző kapcsán érdemes még szót ejteni még egy dologról: gondoljunk csak a window.onload, element.onclick és hasonló JS kódjainkra: az elv ugyanaz, amikor egy esemény bekövetkezett (betöltött az oldal, a user ráklikkelt valamire stb.), a böngésző szól a JavaScript futtatónak, ami meghívja az erre az eseményre beállított eseménykezelőt)
Általában a Gtk programjaink a következőképp épülnek fel: felépítjük azokat az ablakokat, amelyek láthatóak induláskor, beállítjuk az eseménykezelőket, majd meghívjuk a Gtk::main() metódust. Innentől kezdve a felhasználóval való kommunikációt a Gtk végzi, az frissíti a megjelenített ablakok(at), az értesíti a kódunkat az események beállásáról (a user bezárta az ablakot, a user ide kattintott) stb. Ha most ránézünk a példaprogram kódjára, a legalján a következő sorokat láthatjuk:
1 2 | $mcw = new MailNotifierWindow(parse_ini_file('mailboxes.ini', 1), $orientation); Gtk::Main(); |
A MailNotifierWindow osztály példányosításakor a konstruktor (példányosításkor meghívásra kerülő metódus) létrehozza a GUI elemeket, beállítja az eseménykezelőket és elindítja az időzítőket (ezekről később). Majd a Gtk::Main() híváskor a Gtk veszi át az irányítást, megjeleníti a megjelenítendő ablakokat és elemeket, elkezdi a fő ciklust.
Annak, hogy a PHP-ben névtelen függvények létrehozására csak a viccnek is rossz create_function() függvény áll rendelkezésünkre a GTK programozáskor előnyei is vannak: mivel a create_function-t nem (nagyon) fogjuk használni, hanem rendes függvényeket készítünk, következetes elnevezési elveket használva később is egyszerűen áttekinthetővé tehetjük a kódunkat. A fenti pszeudókódban használtam a button_cancel_onclick() függvénynevet. Ugyanezen az elven, a „Cancel” feliratú gomb lenyomásakor (felengedesékor, egész pontosan) végrehajtandó függvény lehet például: button_cancel_onreleased. Az első rész az elem típusa (gomb), a második egy azonosító (cancel), a harmadik pedig az on string és az esemény nevének együttese. Ha a gombot úgy hozzuk létre, hogy külön osztályt származtatunk neki a GTKButton() osztályból, akkor az első két rész elhagyható, és csak a onreleased() lehet a metódus neve.
A fenti „teszt.php” kódjában nem volt Gtk::main() hívás, ott a run metódus indítja a ciklust, majd tér belőle vissza, amikor a user választ adott. Ez a metódus a GtkDialog osztálynak és az abból származtatott osztályok sajátja, így saját ablakoknál nem használható (hacsak nem a GtkDialog-ból származtatjuk őket). Ennek használatakor azonban a Gtk fő hurok az eredeti ablakunkra (parent ablak) vonatkozóan felfüggesztésre kerül, így a szülő ablak inaktívvá válik az új ablak bezárásáig, vagy amíg nem érkezik válasz a felhasználótól.
Most jött el az ideje, hogy a PHP-GTK dokumentációt is linkeljem. Ugyanis nagyon gyakran fogjuk ezt böngészni, ha PHP-GTK-val dolgozunk, és nagyon hasznos. Minden esemény (itt Signal néven futnak), minden osztály és azok minden metódusa megtalálható itt. És fontos ismernünk a címet, elég csak a megnézni az osztály hierachiát és azt több, mint száz osztályt, amit használhatunk.
Az eseménykezelők beállítása a connect_* metódusokkal (connect, connect_after, connect_object, connect_object_after, connect_simple, connect_simple_after) történik. Az egyes osztályok Signaljeit lekérdezhetjük a következő módon:
1 2 3 | <?php var_dump(GObject::signal_list_names(Osztaly::gtype)); ?> |
Többnyire a fő ablakunk bezárásakor szeretnénk kilépni a programból is (kivételhez nem kell messzire menni: a példprogram), így az ablakunk destroy eseményéhez kössük a Gtk::main_quit() metódust.
1 2 3 4 | <?php $window = new GTKWindow(); $window->connect_simple_after('destroy', array('GTK', 'main_quit')); ?> |
Természetesen ezeken az eseményekezelőkön kívül is megkaphatjuk a vezérlést a Gtk-tól: időzítők használatával (Gtk::timeout_add(), Gtk::timeout_remove), az üresjáratban meghívásra kerülő függvényekkel (Gtk::idle_add(), Gtk::idle_remove()) valamint ha mi magunk hívjuk meg a main függvény egy-egy lefutását (Gtk::main_iteration(), Gtk::main_iteration_do()). Előbbit használjuk a példaprogramban az e-mail fiókok ellenőrzésére.
Az eseményvezérletségről kezdésnek talán ennyi elég, ha ezt a különbséget értjük, akkor a Gtk manual alapján nekiállhatunk saját programjaink megírásának.
Még fontos kiemelni, hogy miért is lett csupán Windows-kompatibilis a példakód: a PHP-GTK programok meglehetősen bonyolult felépítése miatt. Négy szint működteti a kódjainkat: a Windows, a PHP értelmező (php-win.exe), a PHP-GTK modul (ext/php_gtk2.dll) és a GTK (libgtk-win32.2.0.0.dll). Nagyjából a következők történnek a háttérben: a PHP elér a Gtk::main() hívásig, értesíti a PHP-GTK-t, ami értesíti a GTK-t, amely elindítja a fő ciklust (közben kommunikál a Windowssal). Amikor bármilyen esemény bekövetkezik, „visszaszól” a PHP-GTK modulnak, ami szól a PHP-nek, ami elindítja az eseménykezelőt – és itt van a problémás rész: a fő ciklus várakozik, amíg az esemény-kezelő nem végez. Így a grafikus felület nem kerül frissítésre („kifagy a program”). Éppen ezért a PHP beépített programfuttató függvényei (exec, system, passthru, proc_open, popen) esélytelenek, mivel mind túl sok időt tölt el, mielőtt visszaadná a vezérlést a PHP-nek. Kerülő megoldásként egy COM objektumot (WScript.shell) betöltve indítjuk el a kliens programot, így elég gyorsan visszakapjuk a vezérlést ahhoz, hogy ne tűnjön a programunk lefagyottnak. Cserébe feladtuk a Linux támogatását, mivel a COM objektumok csak Windows alatt támogatottak. Éppen ezért, ha egy sokáig tartó eseménykezelőt írunk, akkor érdemes a Gtk::main_iteration() függvényét hívogatni, amikor éppen megkaptuk a vezérlést vagy nagyon optimalizálni a kódunkat.
A PHP-GTK hasznos eszközünk lehet, mind ha gyorsan egyszerűbb programokat akarunk összeütni, mind nagyobb fejlesztésekkor. A több operációs rendszer támogatása, a jól felépített objektum-rendszer és a könnyű tanulhatóság mind mellette szól. Ha bármilyen kérdés van a PHP-GTK-ról a fórum megfelelő topicjában fel lehet tenni!
PHP Desktop Aplications Development
Itt van egy angol nyelvü cikk ami inkább az alapokat mutaja be, és a webes fejlesztéstől való különbségre helyezi a hangsúlyt.
Szia!
Ne haragudj de az írást olvashatatlanná teszik a jobb oldali kis ablakok. A sorok végei teljesen nem látszanak a szövegtől ami jelentősen megnehezíti az olvasást. Firefox és IE alatt egyaránt ez a helyzet, nem lehetne valahogy orvosolni ezt a problémát?
Szia Jani, köszi hogy szóltál, a hibát egy lezáratlan kódrészlet okozta, most javítottam.
Én köszönöm, hogy most már rendesen eltudom olvasni ezt a remek leírást :)