Delphi szoftverfejlesztés haladóknak
Salga Péter
DE, AVK Gazdasági- és Agrárinformatikai Tanszék
1
Tartalomjegyzék 1. 2.
Bevezetés............................................................................................................ 3 MDI alkalmazások létrehozása............................................................................ 4 a) Egy kis MDI-elmélet ......................................................................................... 4 b) Készítsük el a keretablakot! ............................................................................. 5 c) MDI függvények és használatuk ...................................................................... 9 Csoportmunka......................................................................................................... 9 Kötelező feladatok ................................................................................................ 10 Szorgalmi feladatok .............................................................................................. 10 3. Öröklődés, ősök ................................................................................................ 11 a) Objektumorientált programozás ..................................................................... 11 b) Öröklődés ...................................................................................................... 13 c) Származtatás az alapformból ........................................................................ 14 Csoportmunka....................................................................................................... 18 Kötelező feladatok ................................................................................................ 18 Szorgalmi feladatok .............................................................................................. 19 4. Komponensek ................................................................................................... 20 a) Bevezető........................................................................................................ 20 b) Komponenscsomagok ................................................................................... 20 c) A komponenskészítés szabályai .................................................................... 21 d) Az alapvető komponensosztályok.................................................................. 22 e) Komponenscsomag telepítése....................................................................... 22 f) Komponens minta (template) készítése......................................................... 27 Csoportmunka....................................................................................................... 29 Függvények, eljárások .......................................................................................... 29 Kötelező feladatok ................................................................................................ 30 Szorgalmi feladatok .............................................................................................. 31 5. Komponensfejlesztés ........................................................................................ 32 a) Miért szoktak az emberek komponenseket írni?............................................ 32 b) Miből célszerű származtatni az új komponenst?............................................ 32 c) TElsoSajatKomponensem ............................................................................. 33 d) Események létrehozása................................................................................. 36 e) TskBetuKombo .............................................................................................. 38 Csoportmunka....................................................................................................... 40 Változók használata .............................................................................................. 40 Kötelező feladatok ................................................................................................ 41 Szorgalmi feladatok .............................................................................................. 42 6. Adatbáziskezelés alapjai ................................................................................... 43 a) Az adatbáziskezelés fogalma ........................................................................ 43 b) Programozási környezetünk frissítése ........................................................... 43 c) BDE ............................................................................................................... 45 d) Egy egyszerű BDE-alkalmazás..................... Hiba! A könyvjelző nem létezik.
2
„Gyakorlat nélkül az elmélet olyan, mintha egy almafát ültetnénk a kertünkben, gondoznánk, nevelnénk, figyelnénk, amikor virágba borul, de amikor almák teremnek rajta egyet sem szakítanánk le, hogy megkóstoljuk.”
1. Bevezetés Az elmúlt félév során remélhetőleg megismerted a Windows szoftverfejlesztés szépségeit, rájöhettél arra, hogy egy egyszerűbb program megírásához nem kell különleges, mágikus képesség, elég az egyszerűbb fogalmak ismerete és logikus gondolkodás. A második félévben megpróbálunk bevezetni Téged a programozás mélyebb bugyraiba, ahol olyan fogalmakkal fogsz találkozni, amelyek már a modern programozáshoz szükségesek. Manapság a szoftverfejlesztés csapatmunka, nagyobb rendszerek kifejlesztését ugyanis csak a legritkább esetben képes egyetlen ember véghezvinni. A fejezetek végén a Csoportmunka részben megismertetünk a team-munkát támogató eszközökkel, és azokkal a fogalmakkal, módszerekkel, amelyek egy csapat segítségére lehetnek egy nagyobb rendszer fejlesztésének kézben tartására. Magyarországon a programozási feladatok túlnyomó része valamilyen üzleti, gazdasági, ügyviteli probléma megoldásához kötődik, és ezek a feladatok szinte mindig igényelnek adatbázis-programozást. A félév során megismerkedhetsz az adatbázisok delphis kezelésének alapjaival, és megtanulhatod a Delphi beépített adatbázisának, az Interbase-nek az alapjait, valamint a kimutatás-készítés eszközeit. A modern alkalmazásokban elengedhetetlen az Internetes programozás, többszál technika alkalmazása, de ejtünk szót az XML-ről, komponenskészítésről, DLL, COM, ActiveX programozásról és az ADO-ról. A félév során egy kölcsönző ügyviteli rendszerét készítjük el, amire néha majd olyan funkciókat is ráerőltetünk, amelyek nem szükségesek feltétlenül minden kölcsönzőben. Célunk az, hogy a fentebb leírt fogalmakat és módszereket megismerd, és hogy megfelelő előkészítést adjunk a félév végén végrehajtásra kerülő önálló csapatmunkában elkészítendő feladathoz. Nagyon fontos, hogy az egyes fejezetek végén található feladatokat vedd nagyon komolyan! Ahhoz, hogy elsajátítsd az anyagot, feltétlenül szükséges, hogy ezen feladatok mindegyikét maradéktalanul megoldd. Ha nehézséged akadna egyegy feladattal, akkor azt jelezd tanáraidnak. Legyél mindig kritikus önmagaddal, de a könyv gyakorlataival és megoldásaival szemben is. A jegyzetben felfedezett hibákat, nem működő programrészleteket, légy szíves, jelezd a
[email protected] e-mail címen. Tartsd szem előtt azt is, hogy a Delphi fejlesztőkörnyezete is tartalmazhat hibákat, amiket meg kell tanulni a kikerülni, és hogy rendszerint nem csak egyetlen jó megoldás létezik! Ha a későbbiekben nem akarsz szoftverfejlesztéssel foglalkozni, akkor fogd fel úgy ezt a félévet, mint annak megismerését, mi mindent lehet ma megvalósítani a modern fejlesztő eszközökkel. Amennyiben a későbbiekben is akarsz még írni programokat, akkor ez a félév jó alapozás lehet egy olyan hosszú és göröngyös utazáshoz, amely során tapasztalt szoftverfejlesztő válhat belőled. Így vagy úgy, mi kívánunk Neked ehhez a félévhez nagyon sok kitartást és azt, hogy a gyakorlás során felmerülő apró problémáidhoz társuljon mindig a megoldás öröme. 3
2. MDI alkalmazások létrehozása a) Egy kis MDI-elmélet Amikor elindítunk egy szövegszerkesztő vagy táblázatkezelő programot, lehetőségünk van egyszerre több dokumentum megnyitására, az ún. „keretablak”-on belül, amelyben kezelni tudjuk ezeket a dokumentumokat. A Windows jegyzettömbje vagy azok a programok, amelyeket a múlt félévben írtatok nem ilyenek. Ezek a korábban megismert alkalmazások egyszerre csak egy ablakot tudnak kezelni, és amikor egy másik ablak megjelenítését akarjuk elérni, akkor külön párbeszédablakok segítségével tudjuk ezt elérni.
1.ábra A Delphiben természetesen van lehetőség több ablakot kezelő alkalmazások írására. Tulajdonképpen a Delphi is ilyen alkalmazás (annak egy speciális formája), mivel azonban hiányzik a főablak háttere és néhány más tulajdonsága, egy kicsit ezt nehezebb felismerni. Azokat az alkalmazásokat, amelyek egy főformon belül megjelenő több formból állnak MDI (Multiple Document Interface), vagy többablakos felületű programnak nevezzük (1. ábra). Ez a technika általánosan használatos a Windows-os alkalmazásokban, ezért néhány éve már, ha komolyabb programot írtak – hacsak a program valamelyik funkciója ezt nem tette lehetővé – az mindig MDI alkalmazás volt.
4
A Windows 95 operációs rendszer volt az előszele annak a divatnak, ami ma is virágkorát éli az operációs rendszer felületek és az irodai programcsomagok körében, amit egydokumentumos felületnek nevezünk. Az Office 2000 már teljes mértékben SDI (Single Document Interface) – egydokumentumos felület. Ez a technika - elméleti szinten – ugyanaz, ahogyan az előző félévben készítettétek a programjaitokat, de természetesen annak egy tervezésben, design-ban kifinomultabb módja. Az ügyviteli programok legnagyobb része még ma is MDI szemlélettel készül, mivel az üzleti folyamatok követéséhez közelebb áll ez a szemléletmód. Ezért választjuk a mi kölcsönző programunk számára is ezt a megoldást. Az MDI alkalmazás főablaka úgy viselkedik, mint egy tároló vagy keret, rendelkezik saját menüvel, és ha a főablakot mozgatjuk, akkor minden egyes objektum, ami benne található vele együtt mozog. A főablakot szülőablaknak vagy keretnek nevezzük, míg a keretablakban nyíló ablakokat gyerekablakoknak. A gyerekablakok csak a keretablak területén belül helyezkedhetnek el, és bármilyen típusúak lehetnek. A gyerekablakok ugyanazokkal a tulajdonságokkal rendelkezhetnek, mint a szülőjük, semmilyen korlátozás nincs rájuk, viszont a keretablakuk nélkül nem létezhetnek. Tehát ha bezárjuk a főablakot, akkor az összes gyerekablak is bezáródik. Ha meg akarjuk változtatni a keretablak és a gyerekablakok viszonyát vagy mélyebb tulajdonságait, akkor nem kerülhetjük el az API (Application Programming Interface) programozást, ami a Windows operációs rendszer beépített interfésze, gyakorlatilag függvények gyűjteménye a windows-üzenetek kezelésére. Bár egy gyakorlott programozónak elengedhetetlen az API függvények ismerete, és a komponensek létrehozásánál utalni fogunk még rájuk, ennek a jegyzetnek nem témája az API programozás. Ha érdeklődsz ebben a témában, akkor ajánljuk figyelmedbe a Delphi Developers’s Handbook című könyvet (1998, Sybex).
b) Készítsük el a keretablakot! Válasszuk a File → New menüpontot, majd a megjelenő ablakon váltsunk a Projects oldalra és válasszuk ki az „MDI Application” ikont (2. ábra).
2. ábra 5
3. ábra Az OK gomb megnyomása után a felugró ablakban el kell mentenünk a projectet megadva a munkakönyvtárat (jegyezzük meg az elérési utat!), és már működik is! Indítsuk el a programot, és próbáljuk ki mi mindent tudunk vele csinálni! Nyissunk meg új gyerekablakokat, próbálgassuk a menüpontokat és az eszköztári gombokat! Ha már mindent megnéztünk, akkor térjünk vissza a Delphi IDE-hez a programunk bezárásával. Ha a főablakban voltak megnyitott gyerekablakok, akkor az alkalmazással ők is automatikusan bezáródtak. A View → Projectmanager menüpont kiválasztásával figyeljük meg, hogy projectünk 3 unitból és 3 formból áll: AboutBox (Névjegy), ChildWin (Gyerekablak), Main (Keretablak). Ezt ellenőrizhetjük egy fájlkezelő alkalmazásban is, ha megnézzük az elmentett projekt fájljait. Nézzünk bele a Keretform kódjába! A New menüpont elindításánál a következőket találjuk: procedure TMainForm.FileNew1Execute(Sender: TObject); begin CreateMDIChild('NONAME' + IntToStr(MDIChildCount + 1)); end; A CreateMDIChild egy eljárás, aminek a bemenő paramétere egy string típusú változó, ami a gyerekablak neve lesz. Az eljárás deklarációját sem kell nagyon keresnünk: procedure TMainForm.CreateMDIChild(const Name: string); var Child: TMDIChild; begin { create a new MDI child window } Child := TMDIChild.Create(Application); //megcsináljuk az ablakot
6
Child.Caption := Name; //a bemenő paraméter lesz a form címkéje if FileExists(Name) then //ellenőrzi a kód, hogy van-e ilyen nevű fájlunk Child.Memo1.Lines.LoadFromFile(Name); //ha talál, feltölti a sorait a gyerekablak Memo komponensébe end; Figyeljük meg a típusdeklarációt: Child: TMDIChild;. Keressük meg mi is az a TMDIChild. Ehhez a Project Manager-ben nyissuk meg a Childwin unitot és formot. A form egyetlen Memo komponensből áll és a Code Explorerben a unit típusdeklarációjánál láthatjuk, hogy a TMDIChild a TForm osztályból származik (az örököltetésről és az ősökről nemsokára bővebben is szólunk majd). A keretablak következő eljárása az új MDIChild (azaz gyerekablak) elkészítését végzi: procedure TMainForm.FileNew1Execute(Sender: TObject); begin CreateMDIChild('NONAME' + IntToStr(MDIChildCount + 1)); end; Ebben az eljárásban meghívódik a fentebb deklarált eljárás, és paraméterként átadódik a „NONAME” szóhoz hozzáfűzve a már meglévő gyerekablakok száma + 1, tehát ez lesz a megnyíló ablak címsorában. Ez utóbbit az MDIChildCount nevű függvénnyel kérdezhetjük le. Ha már meglévő fájlt nyitunk meg az alkalmazásunk segítségével, akkor az szöveges formában jelenik meg egy új ablakban. A következő kódrészlet végzi a megnyitást: procedure TMainForm.FileOpen1Execute(Sender: TObject); begin if OpenDialog.Execute then CreateMDIChild(OpenDialog.FileName); end;
4. ábra 7
Az OpenDialog-ot már ismered (a komponens a formon van elhelyezve – 4. ábra) és látható, hogy ebben az eljárásban is a CreateMDIChild-ot használjuk paraméterként a megnyitott fájl elérési útját (!) és nevét adjuk, azaz ez fog látszani a címsorban. Mielőtt a következő eljárás tárgyalásába kezdenénk, indítsuk el újra az alkalmazást és ellenőrizzük, hogy minden rendben működik-e. Ha még nem tettük eddig, akkor nyissuk meg a Help → About menüpontból nyíló AboutBoxot, ami elég kezdetleges még. Bezárva az alkalmazást a Delphiben szabd át ezt a formot kedved szerint. Rakj rá képet, verziószámot és egyéb információkat, amit fontosnak tartasz magadról és a programról közölni a leendő felhasználónak. Ha most visszatérsz a keretablakra, akkor láthatod, hogy ellentétben az SDI alkalmazás formjával, itt már alapból rápakolt a Delphi jónéhány komponenst (4. ábra). Az OpenDialog mellett találunk egy MainMenu komponenst, amire duplán kattintva láthatjuk a menü teljes szerkezetét. Új menüpontot Insert-tel szúrhatunk be, almenüt a Ctrl + jobb nyíl segítségével, törölni pedig a Delete gombbal tudunk (jobb egér kattintásra felugró menüből is elvégezhetőek ezek a műveletek). A menüpontra kattintva az Object Inspector-ban adhatók meg a menüpontok tulajdonságai, pl. a nevük, a Caption (ez a szöveg jelenik meg a menüpontban, figyeljük meg, hogy a „&” karakter után lévő karakter aláhúzással jelölődik, és az alkalmazás megnyitásakor alt + aláhúzott betű lenyomásával aktivizálódik), de képet is adhatunk meg a menüponthoz. Valamennyi menüpontnak az OnClick eseményére lehet kötni az eljárást, amit végre kell hajtania.
5. ábra
8
A MainMenu mellett találunk egy ActionList komponenst, ami arra hivatott, hogy azokat a feladatokat tartsuk nyilván vele, amit az alkalmazás több pontjáról is indíthatunk. Vegyük például az új ablak létrehozását, aminek van menüpontja a főmenüben, de az eszköztáron is találunk ilyen gombot. Ha rákattintunk az eszköztár első gombjára, akkor az Object Inspector Action tulajdonsága mellett a FileNew1 nevet látjuk. Ugyanez a hivatkozás látható a főmenü File → New menüpontjánál (szintén Action property). A FileNew1 az ActionList komponens egyik listaelemére hivatkozik, amit a komponensre duplát kattintva, a File kategórában találsz (5. ábra). Kijelölve a FileNew1 elemet, az Object Inspectorban látható, hogy a Caption, Enabled, HelpContext, Hint, ImageIndex, Shortcut és Visible tulajdonságok és az OnExecute esemény beállítható. Ha egy menüponthoz vagy gombhoz ezek után hozzárendeljük az egyik ActionList elemet, akkor ezek a tulajdonságok automatikusan beíródnak és nem kell egyenként állítgatni őket. Figyeljük meg, hány menüpontnál használja még az alkalmazásunk az ActionList-et! Az ImageList komponensre kettőt kattintva már látható, hogy mire való: ikonképeket lehet betölteni bele, amire hivatkozhatunk az eszköztár gombjainál és a menüpontoknál az ImageIndex tulajdonság használatával. Ez főleg akkor hasznos, ha egy képet több helyen is felhasználunk, hiszen csak egyszer kell eltárolni, így a programunk kevesebb tárhelyet igényel (bár ezek a képecskék elég aprók, de sok kicsi sokra megy). Megemlíthető még a form legalján található Statusbar (státuszsor) komponens, amivel nemsokára részletesebben is foglalkozunk. Az ablakokon még sok minden nem úgy néz ki, ahogyan majd azt az éles alkalmazásunkban szeretnénk, de ezekkel majd a későbbiekben foglalkozunk.
c) MDI függvények és használatuk Van néhány hasznos metódus és tulajdonság, amelyek kizárólag az MDI ablakokhoz kapcsolódnak: • Az ActiveMDIChild az MDI keretform futásidejű, csak olvasható tulajdonsága, amely az egyetlen aktív gyermekablakot adja vissza. A programon belül ennek változtatása a Next vagy a Previous eljárások használatával érhető el. A Next a belső sorrenden belül az aktívat követő gyerekablakot helyezi előtérbe, míg a Previous a megelőzőt. • A ClientHandle tulajdonság a főform belső területét fedő MDI ügyfélablak Windows leíróját tartalmazza. Ezt kell használnunk pl. amikor egy képet akarunk felrakni a keretform hátterének. • MDIChildCount tulajdonság, ahogyan arról már volt szó, a gyerekablakok aktuális számát adja meg. • Az MDIChildren tulajdonság a gyermekablakok tömbje. Ezt és az előző tulajdonságot a gyermekablakok közötti mozgásra használhatjuk, például egy for ciklusban.
Csoportmunka Figyeld meg a kódszerkesztőben a Delphi által generált kódot! Csapatmunkában nagyon fontos, hogy amikor más kódját vagy kénytelen böngészni, akkor az ismerős legyen. Ennek egyik legfontosabb beállítása a behúzás vagy indent. Akár tabulátornak is nevezhetnénk, de ez veszélyes is lehet, ha valaki a „TAB” billentyűhöz rendelné gondolatban az indent fogalmát. 9
Ahogyan a Delphi által generált kódban is láthatod, az alapértelmezett behúzás két szóköz. Ha bármelyik profik által megírt programot nézed meg, ezzel a konvencióval találkozol. Ez tulajdonképpen azt jelenti, hogy amikor új sort kezdesz, ami új szakaszhoz tartozik, akkor két szóközzel mindig beljebb kezded, mint az előző sort. Ezután alkalmazd Te is ezt a konvenciót az általad írt programokban. Természetesen a begin-end blokkok, a kód strukturáltsága – amivel már előző félévben foglalkoztunk – továbbra is nagyon fontos az olvashatóság szempontjából.
Kötelező feladatok 1. A programot, amit írni fogsz magyar emberek fogják használni ezért nagyon fontos, hogy magyar nyelvű felülettel találkozzanak. Magyaríts meg mindent az alkalmazásodban, amivel a felhasználó kapcsolatba kerülhet! Menüpontok, hintek, az ablakok címsorai stb., és ne feledkezz meg a project nevéről sem, amely mint az alkalmazás futtatható állományának neve fog megjelenni! 2. Találd ki, hogy a Childwin unitban miért van szükség a FormClose eljárásban az Action := caFree; kódra! 3. Írj egy rövid metódust az MDI alkalmazásodhoz, ami megnyit 5 ablakot, majd sorban aktívvá teszi őket 5-5 másodpercre! 4. Írj egy olyan ciklust, ami a főablak minden megnyitott gyerekablakát bezárja! A ciklusban használd az MDIChildCount függvényt! 5. Hol van definiálva a Keretform Windows → Cascade menüpontjára végrehajtódó eljárás? 6. Alakítsd át egy múlt félévbeli alkalmazásodat MDI alkalmazássá újraírás nélkül! (Legalább két form kell hozzá, és használd a FormStyle tulajdonságot!)
Szorgalmi feladatok* 1. Készíts egy egyszerű, Jegyzettömbhöz (Notepad) hasonló szövegszerkesztő alkalmazást! 2. Készíts olyan MDI alkalmazást, amelynél a főablak háttere tetszőleges .bmp kiterjesztésű képfájl lehet és a felhasználó beállíthatja a háttérképet! A megoldásban segítséget nyújt Marco Cantú Delphi 5 című könyvének (2000, Kiskapu) első kötete (8. fejezet). A kész alkalmazást teszteld alaposan!
*
Ha szorgalmi feladatot oldottál meg, mindenképpen küldd el a teljes kódot a
[email protected] vagy a
[email protected] címek valamelyikére. A beküldött megoldásokat értékeljük, és év végén szerény „anyagi” jutalmat, és nagy erkölcsi elismerést ☺ kapnak a legjobbak!
10
3. Öröklődés, ősök a) Objektumorientált programozás Bár az előző félévben már volt róla szó, most átismételjük az objektumorientált szemlélet néhány alapfogalmát. Az osztály (class) egy programozó által definiált adattípus, melynek van megjelenése és vannak műveletei. Az osztályoknak vannak belső adatai és metódusai (függvények vagy eljárások). Általában akkor érdemes egy osztályt létrehoznunk, ha több hasonló objektumot akarunk csoportosítani benne. Az objektum (object) az osztály egy példánya (instance) vagy az egy, az osztály által meghatározott típusú változó. A program futása közben az objektumok memóriát foglalnak le belső leírásuk számára. Az objektumok és az osztály kapcsolatát leginkább a változók és adattípus kapcsolathoz hasonlíthatjuk. Az osztálydeklarálás módja: type TEnOsztalyom = class Nev, Cim: String; procedure Beallit (n, c: String); function HanyBetusANeve: Integer; end; A Delphiben minden osztály és típus neve elé – hagyományőrző módon – T (type) betűt szokás tenni. A csapatmunka támogatására, de saját kódod olvashatóságának növelésére is érdemes ezt a konvenciót alkalmaznod. Most akkor csináljunk egy új objektumot, ami természetesen a fent deklarált osztály tagja lesz: var EnObjektumom : TEnOsztalyom; begin //létrehozás EnObjektumom:= TEnOsztalyom.Create; //használjuk EnObjektumom.Beallit(’Első’, ’Ez a címe’); ShowMessage(’Ez egy ’ + IntToStr(EnObjektumom.HanyBetusANeve) + ’ karakteres objektum.’); //felszabadítás EnObjektumom.Free; end; Az EnObjektumom.HanyBetusANeve függvényhívás egy kicsit más, mint az EnObjektumom.Nev adatelérés, bár formátumban ugyanúgy néznek ki, ez egy hatékony kényelmi funkció a Delphiben. A Delphiben az osztály létrehozásakor nem jön létre automatikusan az osztály egy példánya, létre kell hoznunk, és amikor létrehozzuk, akkor egy hivatkozás jön 11
létre, amely az objektum helyét jelöli a memóriában (objektumhivatkozási modell). Tehát, amikor deklarálunk egy változót, nem jön létre az új objektum, kizárólag az objektumra mutató hivatkozás számára foglaljuk le a memóriát. Az általunk definiált objektumpéldányokat nekünk kell létrehoznunk a konstruktorukkal, ami a Create metódus. Hogy gyakoroljuk és elmélyítsük néhány fontos fogalom használatát egészítsük ki az előző fejezetbeli MDI alkalmazást úgy, hogy a gyerekformok egérkattintásra gombokat hozzanak létre dinamikusan a felületükön (6. ábra).
1. ábra procedure TMDIGyerek.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Gomb: TButton; begin Gomb:= TButton.Create(self); Gomb.Parent:= self; Gomb.Left:= X; Gomb.Top:= Y; Gomb.Width:= Gomb.Width + 80; Gomb.Caption:= Format('Egy gomb a %d, %d pozíción', [X, Y]); end; Ne felejtsük el felvenni a uses listára a SysUtils unitot, mert ebben van definiálva a Format függvény (hogy mi hol van definiálva, arról legegyszerűbben a 12
Help-ből tájékozódhatunk). Le kell vennünk még az MDIChild-ról a Memo komponenst, hogy hozzáférjünk a keretform munkafelületéhez. Vegyük észre, hogy a MDIGyerek (az MDIChild neveződött át az előző fejezetben erre a névre) helyett a kódban a Self kulcsszót használtuk, ezzel hivatkozunk az aktuális objektumra. Általában nem tapasztalnánk változást, ha a selfet a konkrét form nevére módosítanánk, de ha több példányt is használunk, mint azt itt az MDI alkalmazásunkban tesszük, akkor egyszerre nem tudunk a kódban több példányt megnevezni, és mindig a megnevezett formon jelennének meg a gombok, ezért nagyon hasznos a Self kulcsszó. A Create konstruktor feladata, hogy memóriát foglaljon le az objektum (az osztály egy példánya) számára. A konstruktor által visszaadott példányt hozzárendelhetjük egy változóhoz (Gomb:= TButton.Create(self);) , így a későbbiekben használhatjuk. A Create konstruktor minden objektumok ősének a TObject objektumnak a konstruktora, és mivel miden objektum a TObjectből öröklődik, öröklik a Create metódust is. Az alapértelmezett Create konstruktor az új példány minden adatát nullára állítja. Ha nem nulla kezdőértékekkel szeretnénk objektumot létrehozni, akkor saját konstruktort kell írnunk. Talán felmerült benned, hogy ha létrehozunk valamit, akkor azt – ha már nem használjuk – el is kell tűntetnünk. Szerencsére a Delphiben nem lehet hosszú távon szemetelni a memóriába, mert a nem használt objektumokat felszabadítja a memóriából a rendszer. De azért nem szabad felelőtlenül kezelni az általunk létrehozott objektumokat, hanem ha már nincsen rájuk szükségünk be kell dobnunk őket a virtuális kukába. Destruktornak nevezzük azt az eljárást, amely felszabadítja az objektumot a memóriából. Ez általában a TObject-ből származó destroy metódus. Nem ajánlott viszont a destroy-t használni, csak kivételes esetekben. Használjuk inkább a Free metódust, amely csak abban az esetben hívja meg a destroy-t, ha az objektum létezik (vagyis nem nil). A Free nem állítja automatikusan nil-re a objektum értékét, mivel az objektum nem tudja, hogy mely változók hivatkoznak rá, így nem is tudja nilre állítani azokat. Ezt a nagy hiányosságot a FreeAndNil(TObject) metódus hívásával tudjuk kiküszöbölni, ami nemcsak felszabadítja, de nil-re is állítja az objektum hivatkozásait.
b) Öröklődés Az objektumorientált programozás lényege, hogy a korábban már megírt osztály módosított változatát használni tudjuk. Ezt nevezzük örököltetésnek, ami a biológiából vett fogalom, de jól előrejelzi az öröklődés tulajdonságait. A legegyszerűbb az lenne, ha a módosítást az eredeti osztály kódjába írjuk bele, viszont akkor az eredeti osztályt az eredeti formájában nem tudjuk többé használni. Logikusan gondolkodva, hogy mindkét osztályt használni tudjuk, először készítsünk másolatot az eredeti osztályról, utána változtassuk meg a másolat kódját, majd mentsük el más néven. Az így létrehozott kód működik, de ha egy új metódust akarunk implementálni, mind az eredeti mind a másolt osztályba, akkor mindkét osztály kódjához hozzá kell nyúlnunk. Az öröklődés megoldja ezeket a problémákat. Az öröklést már sokkal gyakrabban használtad, mint gondolnád. Ha például belenézel az MDIGyerek kódjába, akkor a következő deklarációs rész egy öröklés:
13
type TMDIGyerek = class(TForm)… Azaz a TMDIGyerek osztály örökli a TForm osztály összes metódusát, mezőjét, tulajdonságát és eseményét. A TForm összes nyilvános metódusát alkalmazhatjuk a a TMDIGyerek osztály példányaira is. Ennek segítségével egy formot származtathatunk egyszerűen egy másik formból, majd kiegészíthetjük azt új komponensekkel, illetve módosíthatjuk a meglévő komponensek tulajdonságait. Ha olyan alkalmazást szeretnénk írni, amely sok ablakot tartalmaz és ezek között sok hasonló van (a kölcsönző programunk éppen ilyen), akkor létrehozhatunk egy alapformot olyan komponensekkel, amelyek mindegyik ablakon rajta lesznek, megfelelő eseménykezelő eljárásokat definiálhatunk. Az objektumorientált programozás legfontosabb előnye, hogy ha lecseréljük vagy megváltoztatjuk az eredeti formot, amiből örököltettük a többit, akkor a származtatott formokon is érvényesülnek a változások.
c) Származtatás az alapformból Az MDI alkalmazásunkból először tisztítsuk ki azt a kódot, amit az imént raktunk bele, vagyis az egérkattintásra megjelenő gombokat. Erre a funkcióra természetesen nem lesz szükségünk a kölcsönző-programban. Viszont a Memo komponens se szükséges a gyerekablakok felületére. Ezek után alakítsd a gyerekablakot olyan jellegűre, amint azt a 7. ábrán láthatod.
2. ábra A gombok TBitBtn komponensek, figyeld meg rajtuk, hogy a Caption első betűi alá vannak húzva. Ezek a betűk gyorsbillentyűként működnek, ha például 14
megnyomod az ALT+A billentyűkombinációt, akkor az „Adatmentés” gomb aktiválódik. Ennek megvalósítása hasonlóan történik, mint azt a TMainMenu komponensnél láthattuk. A gombok fölött egy DBGrid komponens található, amit a „Data Controls” palettán találsz a Delphiben. Ebben a gridben jelennek meg majd az adatbázis adatai, és itt lehet majd szerkeszteni, törölni őket vagy éppen egy új rekordot hozzájuk adni. Ezután írjuk meg a Kilépés gomb kattintásra végrehajtódó eseménykezelőjét: procedure TMDIGyerek.btnKilepesClick(Sender: TObject); begin if MessageDlg('Valóban be akarod zárni az ablakot?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then Close else ShowMessage('Akkor meg ne nyomogasd a "Kilépés" gombot!'); end; Ezután futtassuk a programot és nézzük meg az eredményt. Mivel ősformot készítünk, amelynek örököltetett példányait fogjuk majd használni alkalmazásunkban, ezért nagyon fontos, hogy csak a tökéletes kinézettel elégedjünk meg. Ha például valami 8. ábrához hasonlót látunk, akkor vegyük észre, hogy a gombok a gyerekablak alján a form átméretezésével elég szokatlan helyre kerülhetnek.
3. ábra
15
Ennek kiküszöbölésére használjuk a gombok Anchors propertyjét, és ami még hasznosabb: a TPanel komponenst. A „Standard” fülön található TPanel komponenst akkor fogjuk a legjobban értékelni, ha több részből álló formokkal akarunk dolgozni. A komponensek csoportosításánál, a Splitter-rel történő méretezésnél, átméretezésnél előforduló torzulási problémáknál mutatkozik meg az igazi ereje ennek a komponensnek. A Panel komponenst, tegyük a gombok alá, ügyelve arra, hogy kitöröljük a Caption tulajdonságához rendelt szöveget (Panel1), mert az igencsak csúnyán mutatna, ha az ablak átméretezésnél az egyik gom alól kikandikálna a felirat. Az ablak beállításánál ügyelj még a komponensek bejárási sorredjére (Taborder tulajdonságok) és ha kedved tartja kiegészítheted az OnClose eseménykezelőt is egy kérdéssel, amiben meggyőződsz, hogy komoly kilépési szándékkal nyomogatja-e a felhasználó az ablak címsorában lévő (X) bezárás gombot. Ha ezek után úgy érzed, hogy az ablakod tökéletes, akkor el is kezdhetjük az ősform elkészítését és a származtatást. A legtöbb Delphi könyv ehelyütt leírja, hogy hogyan kell az Object Repositoryhoz új elemeket - jelen esetben formokat – adni (Tools → Repository…). Valójában sokkal egyszerűbb a helyzet, ha csak örököltetni akarunk. Nyissuk meg a File → New menüpontot és lépjünk át a project-ünk nevét viselő oldalra (9. ábra).
4. ábra Az alkalmazásunk összes formját megtalálhatjuk itt, és kiválasztva a formot majd megnyomva az OK gombot (csak az Inherit opció aktív, de nekünk nem is kell más, mivel örököltetni akarunk), már el is készült az első örököltetett ablakunk (10. ábra).
16
5. ábra A szép, új gyerekablakunk örökölte az őse összes tulajdonságát. Itt az ideje, hogy leteszteljük, valóban működik-e az objektum-orientált programozás a Delphiben. Változtassunk meg az ősformon (ezután Ős) valamit, pl. a gomb méretét (a gyerekformon (ezután Gyerek) vele együtt változik a tulajdonság), rakjunk fel új komponenst, majd töröljük, stb. Folyamodjunk cselszövéshez: változtassuk meg a Gyereken az „Adatmentés” gomb Height propertyjét 35 pontra. Ezután térjünk vissza az ősre és állítsuk ott az „Adatmentés” gomb Height tulajdonságát 15 pontra. Nézzük meg a Gyereket!... Valami érdekes történt, a gyereken nem változott a property értéke. Próbáljuk a Width propertyt nagyobbra állítani: a Gyereken az „Adatmentés” gomb Width propertyje szolgai módon követi a változást. Hogy is van ez? Jegyezzük meg, hogy ha megváltoztattuk egy örökölt komponens valamely tulajdonságát, és később ugyanezt a tulajdonságot módosítjuk az eredeti komponensnél, akkor a változtatás az örökölt komponensben nem jelenik meg. Természetesen a nem változtatott tulajdonságok továbbra is öröklődnek.
6. ábra
17
Van azonban mód a szinkronizálásra. Ha visszatérünk a Gyerekhez és az „Adatmentés” gombra kattintva az Object Inspector helyi menüjében a Height propertynél kiválasztjuk a „Revert to inherited” menüpontot, a tulajdonság visszaállítódik az eredetire (11. ábra). Amit még megtehetünk, hogy az Őst valami Őshöz méltó névre kereszteljük át, pl. OsGyerek MDIGyerek helyett.
Csoportmunka Miért van szükség kódolási konvenciókra? A cél, hogy az egy projectben dolgozók olyan forráskódot készítsenek, amely a többiek által is könnyen átlátható és szükség esetén gyorsan javítható. Ehhez szükséges néhány alapelv bevezetése a program szintaxisára vonatkozólag és célszerű néhány alapvető programozási technikával kapcsolatos dologban is hasonlóan gondolkozni/dolgozni. Természetesen ez nem jelenti azt, hogy nem lehetnek mindenkinek egyéni ötletei egy adott probléma implementálását illetően, de ha ezt olyan formában készíti el, hogy mindenkinek már ránézésre ismerős legyen a kód, akkor sokkal hatékonyabb lesz a csapatmunka. Általános elvek: - Szóközt használjunk tabulátor helyett, szintenként kettőt; - Ne írjunk 80 karakternél hosszabb sorokat; hosszú kifejezéseknél a következő sort bentebb kezdjük (2 szóközzel!) - Lehetőleg minden függvény férjen bele egy „képernyőbe” - Megjegyzések írása: először is írjunk minden hosszabb ill. nem triviális függvényhez, eljáráshoz vagy akár ciklushoz megjegyzést, amiből egyértelműen kiderül, hogy mi a feladata az adott kódrészletnek; - Írjunk minden Delphi reserved és keyword-öt kisbetűvel; A következő fejezetek Csoportmunka bekezdéseiben a kódolási konvenciók ajánlott formátumát fogjuk részletezni.
Kötelező feladatok 1. Örököltess az MDIGyerek formból egy példányt, ez után írd úgy át a kódot, hogy a származtatott példány az MDIKeret származtatott példánya legyen! 2. Az előbbi feladatban kézzel származtatott osztálynak írj egy konstruktort, ami bizonyos tulajdonságokat adott értékekre állít be, ezután szúrj be egy új menüpontot a Fájl menübe, amire rákötöd újonnan létrehozott konstruktorunkat. Nézd meg futás közben hogy néz ki, amit elkövettél! 3. Az új formod bezárásánál próbáld ki azokat a memória-felszabadítási lehetőségeket, amelyekről ebben a fejezetben tanultál! 4. Írj egy osztályt, ami bemeneti értékként egy személy magasságát kéri centiméterben, majd visszaadja azt méterben! 5. Lazíts! Menj ki a szabadba, sétálj egyet, vagy nézz meg egy filmet a kedvenceid közül! Megérdemled!
18
Szorgalmi feladatok 1. Készítsd el a gerincesek törzsének osztályhierarchiáját! A rendek és osztályok legyenek egy-egy osztály, definiálj hozzájuk metódusokat (pl. repül), függvényeket (megeszi_e(TGerinces) : boolean), és tulajdonságokat (szCsíkos). Származtass egy példányt valamelyik osztályból, és keltsd életre!
19
4. Komponensek a) Bevezető Sok Delphi-programozó a készen kapott Delphi-s komponenseket használja, de sokszor szükség lehet a meglévő komponensek módosítására, vagy teljesen új komponensek írására a munka egyszerűbbé tételére. Amikor egy új komponenst készítünk, akkor mindig a VCL (Visual Component Library) egy már meglévő osztályát bővítjük ki. Ehhez az Object Pascal nyelv számos olyan tulajdonságát használni kell, amelyekre a kész komponensek írásakor ritkán van szükség. Nem szabad azonban megijedned a feladattól, és ha problémákba ütközöl, akkor lapozz fel egy Object Pascal referenciát, vagy a Delphi Help-et segítségért. A Delphi komponenseket osztályoknak is tekinthetjük, a VCL pedig nem más, mint a Delphi osztályokat leíró komponensek gyűjteménye. Minden alkalommal, amikor egy komponenseket tartalmazó új csomagot (package) adunk a Delphihez, valójában a VCL-t bővítjük ki egy újabb osztállyal. Az új osztály természetesen mindig egy már létező komponens osztályának leszármazottja lesz (a létező komponens osztálya az ős), annak örökölt tulajdonságait bővíti ki új lehetőségekkel. A fentiekből sejthető, hogy ebben a fejezetben nagyrészt az előző fejezetben tanultakra támaszkodunk.
b) Komponenscsomagok A komponensek a Delphiben úgynevezett komponenscsomagokba kerülnek. Minden komponenscsomagnak BPL (Borland Package Library) a kiterjesztése, ami mellesleg egy DLL* (dinamikus csatolású könyvtár) csak álruhában. A csomagoknak két formája van: fejlesztési idejű (design time) komponenscsomagok, ezeket fejlesztési időben csak a Delphi használja, és a futásidejűek (run-time), amelyek a kész alkalmazásokhoz (is) kapcsolódnak. A $DESIGNONLY vagy $RUNONLY kapcsolók határozzák meg a csomag típusát. Amikor egy csomagot telepítünk, akkor az IDE ellenőrzi, hogy melyik kapcsolót használtuk, és eldönti, hogy a felhasználó telepítheti-e a csomagot és hogy azt hozzá kell-e adni a futásidejű csomagok listájához. Mivel a két kapcsoló nem zárja ki kölcsönösen egymást, ezért négy típus van, két általánosabb és két különlegesebb változat: •
A fejlesztési idejű komponensek telepíthetőek a Delphi fejlesztői környezetébe. Rendszerint a komponens tervezési idejű részeit, a tulajdonságszerkesztőket és a regisztrációs kódot tartalmazzák. A tervezési idejű csomagok komponenseinek kódja rendszerint statikusan szerkesztődik be a futtatható állományba, a megfelelő DCU (Delphi Compiled Unit) fájl kódjának felhasználásával. A statikus beszerkesztés vagy statikus kötés azt
*
A DLL-ek futtatható állományok, amelyek függvényeket és osztályokat tartalmaznak. Ezeket programok illetve más dll-ek használhatják futásidőben. Legfőbb előnyük, hogy ha ugyanarra a dll-re egyszerre több programnak is szüksége van, annak csak egy példányát kell tárolnunk a háttértárolón, és a futtatható állomány mérete is kisebb lesz. Másik előnyük, hogy egy több modulból álló rendszer részeit dll-ekbe fordítva könnyedén tudjuk a rendszerünket a dll-ek másolgatásával is testreszabni. A jegyzet egy külön fejezetében fogunk majd foglalkozni a dll-ek készítésével.
20
•
• •
jelenti, hogy a metódushívást már általában a fordító feloldja, és helyettesíti azt annak a memóriacímnek a meghívásával, ahol az adott függvény vagy eljárás található (azaz a függvény címével). Ezzel szemben a dinamikus beszerkesztés vagy más néven késői kötés esetében a metódushívás pontos címét a hívást kezdeményező példány típusa határozza meg futásidőben. A futásidejű komponenscsomagok-at a Delphi-programok futás közben veszik igénybe. A Delphi környezetbe nem telepíthetők, de automatikusan felkerülnek a futásidejű csomagok listájára, ha egy telepített tervezési idejű csomagnak szüksége van rájuk. A futásidejű csomagok rendszerint tartalmazzák a komponensosztályok kódját, de a fejlesztést nem támogatják (ez azért jó, mert így a futtatható fájl mellé a komponenskönyvtárak mérete a lehető legkisebb lesz). A futásidejű komponensek azért fontosak, mert a fejlesztett programjaiddal együtt szabadon terjesztheted őket, a konkurens Delphi programozók viszont nem tudják telepíteni őket fejlesztőkörnyezetükbe, így nem tudják felhasználni programjaikhoz sem. A kapcsoló nélküli egyszerű komponenscsomagok nem telepíthetőek és nem kerülnek fel automatikusan a futásidejű csomagok listájára. Ezeket egy másik csomag használhatja segédcsomagként. Azok a csomagok, amelyekhez mindkét kapcsolót megadjuk, telepíthetőek és a futásidejű csomagok listájára is automatikusan felkerülnek. Ezek a csomagok olyan komponenseket tartalmaznak, amelyek kevés fejlesztési támogatást igényelnek. Ezek a komponensek azonban lophatók, és így fáradságos komponensfejlesztési munkák gyümölcseit más programozók is learathatják.
Amikor egy futásidejű komponenst lefordítunk, akkor egy, a lefordított kódot tartalmazó dinamikus csatolású könyvtár (.bpl kiterjesztésű fájl), illetve egy csak szimbóluminformációkat tartalmazó állomány (.dcp kiterjesztésű fájl) jön létre. Ez utóbbit a Delphi fordító használja azért, hogy összegyűjtse a szimbóluminformációkat azokról az egységekről, amelyeket a csomag tartalmaz, anélkül, hogy a .dcu fájlhoz hozzá kellene férnie (mivel ezek nemcsak szimbóluminformációkat, hanem lefordított gépi kódot is tartalmaznak, ezért a hozzáférés sokkal lassabb lenne). Ez csökkenti a fordítási időt, és lehetővé teszi csak a csomag terjesztését, az előre lefordított egységek nélkül. Az előre lefordított egységekre a komponensek statikus beszerkesztése miatt van szükség. A Delphi saját tervezési idejű csomagjainak a nevei a DCL betűkkel kezdődnek (pl. DCLSTD50.BPL), míg a futásidejű csomagok nevei pedig a VCL előtaggal (pl. VCL50.BPL).
c) A komponenskészítés szabályai Ha komponenskészítésre adod a fejed (és kénytelen vagy arra adni, ha jó jegyet akarsz kapni programozásból) érdemes átböngészned a Delphi Component Writer’s Guide súgófájlját, ami a Delphivel együtt feltelepül a számítógépre. Ezeknek a szabályoknak a nagy részét ebből a komponenskészítő útikalauzból vettük. • •
Vizsgáld meg a VCL osztályrendszerének felépítését, és nem árt ha egy rajzot is a kezed ügyében tartasz róla! Kövesd a Delphi szabályait a komponensek elnevezésére!
21
•
• • •
Készíts mindig egyszerű komponenseket, más komponensek alapján dolgozz és kerüld a függőségeket! E három szabály betartásával a komponenseid felhasználóinak éppen olyan könnyű lesz használni azokat, mint a Delphi beépített komponenseit. Használj kivételeket! Ha valami rosszul működne, célszerű, hogy a komponens kivételt váltson ki. Ha erőforrást foglalsz le, védd azt egy tryfinally blokkal, és gondoskodj a destruktor meghívásáról. Komponensírás közben felejtsd el a Delphi vizuális szemléletét, itt valódi kódot kell írnod vizuális segítség nélkül! Kicsit már elkényelmesedtél, de meglátod gyorsan belejössz. Használhatsz (de nem az órán) külső cég által megírt komponenskészítő eszközt is a fejlesztéshez. Ilyen pl. az Developer Express (http://www.devexpress.com).
d) Az alapvető komponensosztályok Új komponens készítésekor általában egy már létezőből vagy a VCL alaposztályai közül egyből indulnak ki. Mindkét esetben a készítendő komponens három tág kategória egyikébe tartozik: •
•
•
TWinControl: minden ablakra épülő (formra tehető) komponens szülőosztálya. Az ebből származó komponensek kaphatnak fókuszt, elkaphatják a rendszer üzeneteit, illetve az API függvények hívásánál ablakleírókat is használhatunk. Ha új vezérlőt akarsz készíteni, akkor a legjobb, ha a TCustomControl osztályból örökölteted, amiben már több hasznos metódus és tulajdonság van beépítve. TGraphicControl: olyan grafikus komponensek szülőosztálya, melyeknek nincsen ablakleírója (ezzel némi Windows erőforrást takarítanak meg). Ezek a komponensek nem kaphatnak fókuszt és nem is válaszolnak a Windows üzenetekre. TComponent: minden komponens (beleértve a vezérlőket is) szülőosztálya. Közvetlen szülőosztályként szoktuk használni nem látható komponensek készítésénél.
e) Komponenscsomag telepítése A következő példában egy már kész, mások által fejlesztett, de a Delphi alap komponenskészletében nem létező komponenst fogunk telepíteni. Első lépésként töltsük le a komponenst a http://www.maxcomponents.net/components.html weblapról. A komponens neve: mxcaLENDAR, és egy csicsás, dátum kiválasztására alkalmas komponens, amit majd használni fogunk a kölcsönző alkalmazásunkban is. Nézd meg hogy hogyan néz ki, olvasd el, hogy mit tud! Az installálás első lépése az mxcalendar_1_21.zip kicsomagolása egy adott helyre, majd a mxCalendar_1_21.exe futtatása (1. ábra).
22
1. ábra
2. ábra A telepítési varázsló végigvezet bennünket a kezdeti lépéseken (2. ábra).
3. ábra
23
Adjunk meg egy mappát arra kérdésre, hogy válasszuk ki a telepítés célkönyvtárát (3. ábra), és a könyvtár választása után jegyezzük meg az elérési utat.
4. ábra Válasszuk a teljes installálást (4. ábra). A telepítő varázsló csupán annyit csinált, hogy az általunk kiválasztott könyvtárba másolta fel a komponens forrásfájljait és a csomagfájlokat a Delphi különböző verzióira (5. ábra).
5. ábra
24
Ahhoz, hogy a komponens a Delphi palettán is megjelenjen, a Delphiben is fel kell installálnunk. A readme.txt fájlban (6. ábra) találunk ehhez kapcsolódóan információt, csak követnünk kell az ajánlott lépéseket.
6. ábra Ehhez nyissuk meg a telepítési célkönyvtárban lévő mxCalendar_d5.dpk fájlt a Delphiben (a „d5” jelzi, hogy ez a Delphi 5-ös verziójához készített csomag). Ekkor a package fastruktúrás ablaka jelenik meg (6. ábra).
7. ábra Nyomd meg az abalakon a „Compile” gombot. Ennek hatására a komponens forráskódja leforítódik bináris formátumba. Ezt ellenőrizheted, ha megnézed a
25
könyvtárat, ahonnan a .dpk-t megnyitottad. Ezután nyomd meg az „Install” gombot, ez elindítja azt a folyamatot, aminek a végén a komponens valóban felkerül a Delphi pallettára, ezt egy üzenet formájában jelzi nekünk a Delphi (7. ábra).
8. ábra
9. ábra Rövid keresés után megtalálhatod az utolsó, Max nevű palettán az mxCalendart (8. ábra), és hogy ne maradjon benned frusztráció, mindenképpen próbáld is ki egy kis példalakalmazásban. Ha a futtataás során, a 9. ábrához hasonló üzenetet ad a a Delphi fordítója, akkor még van egy kis tennivaló a komponensünkkel.
10. ábra A fordító nem találja az „mxCalendar.dcu” állományt. Ez tulajdonképpen azt jelenti, hogy az mxCalendar.pas-t sem találja, pedig saját szemünkkel is meggyőződhetünk, hogy a két fájl létezik, és ott van a helyén, ahová installáltuk a csomagot. A gond az, hogy a Delphit is tájékoztatnunk kell róla, hogy hová raktuk az új komponenst, vagy esetleg egy olyan helyre kell raknunk a lefordított .dcu fájlt (pl.:
26
WINDOWS könyvtár), ahol alapból megnézi a fordítás során. Válasszuk az előbbi megoldást, ami korrektebbnek tűnik! A Component menüből válasszuk az Install Component menüpontot, majd a megjelenő ablak kontroljain (10. ábra) állítsuk be az mxCalendar tulajdonságait. A Unit névnél használjuk a „Browse” gombot, a keresési útba automatikusan beleíródik így a .pas és a .dcu fájlok elérési útja (ezt azért ellenőrizzük), majd a legördülő listából válasszuk ki a csomagfájl nevét. Az OK gomb megnyomásával újra megjelenik a csomag böngésző ablaka, nyomjuk meg újra a compile, majd az install gombot.
11. ábra Ha ezek után újra lefordítjuk teszt alkalamazásunkat az mxCalendar komponessel, akkor már futni fog. Most is bebizonyosodott tehát, mint már annyiszor, hogy nem szabad mindent elhinnünk, amit más programozók írnak nekünk.
f) Komponens minta (template) készítése A komponens minta egy komponens csoport vagy komponensek csoportja, amit módosítani tudsz a kívánt módon és ezután elmented későbbi felhaználásra. Ez még nem igazi komponens-készítés, csak bemelegítés. A komponensminta lehetővé teszi, hogy gyorsan megváltoztassuk a Delphi beépített komponenseit, pl. kontrolljait a saját szánk ízlése szerint. A Delphi Edit mezőjének van néhány beépített tulajdonsága. Pl. ha a felhasználó megnyomja az ENTERt, akkor a formon a „default” gomb válik aktívvá. Ez lehet pl. a bezárás vagy OK gomb is, amire az ablak bezáródik. A DOSban gyakorlott felhasználók viszont hozzászoktak ahhoz, hogy az enter megynomására a következő vezérlőbe ugrik a kurzor. Hogy ezek a felhasználók is jobban magukévá tudják tenni majd készülő kölcsönző programunkat, most készítsünk egy olyan Edit mezőt, ami az ENTER leütésére nem az alapértelmezett vezérlőnek adja át a fókuszt, hanem TAB billentyűként viselkedik és átugrik a bejárási sorrendben következő kontrollra. Ehhez egy új alkalmazásban dobj fel a formra egy edit mezőt, majd az OnKeyPress eseménybe írd be akövetkező kódot:
27
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin if Key = Char(VK_RETURN) then begin Key := #0; PostMessage(Handle, WM_NEXTDLGCTL, 0, 0); end; end; Ez a kód először ellenőrzi, hogy ENTER-t nyomott-e a felhasználó (az enter virtuális kódja: VK_RETURN). Ha ez történt, akkor a Key értéket nullára állítjuk. Erre azért van szükség, mert a Windows alapból csipog, ha entert nyomunk egy edit kontrollban. A következő sorban egy Windows üzenetet postázunk a formnak, ami a bejárási sorrendben a következő kontrollra állítja a fókuszt. Ezután már nincs más hátra, minthogy elmentsük komponens mintaként a létrehozott Edit mezőt. Állítsuk a kontroll Name tulajdonságát „AzEnterIsTab” névre, hogy később könnyen rásimerjünk az új komonensre, és a Text property értékét töröljük ki az Object Inspectorban. Jelöld ki a formon az Edit komponenst, majd válaszd a Component → Create Component Template menüpontot. A Component Template Information dialógusablak jelenik meg (12. ábra).
12. ábra A komponensünk nevét írjuk át TAzEnterIsTab névre, és nyomjuk meg az OK gombot, hogy elmentsük a komponens mintát. Ha most ránézel a komponens palettára láthatod, hogy megjelenik egy Templates fül, és azon a szép, új, általad készített AzEnterIsTab komponens. Gartulálunk! Elkészítetted az első saját komponensedet. Ha kipróbálod ezt az új komponenst egy új formon, akkor láthatod, hogy az OnKeyPress eseménybe beírt kódot is megjegyezte, és most már egy kattintással újra használhatjuk. Egy kis gond is lehet ezzel, pl. ha több ilyen kontrollt akarsz a formra rakni, akkor ugyanaz az OnKeyPress kód sokszorozódik. Erre természetesen nincsen szükség. Hasonló esetben járj úgy el, hogy csak egy TAzEnterIsTab komponenst teszel a formra, a többi legyen TEdit, és a TEdit komponensek OnKeyPress eseményét kell hozzárendelni a TAzEnterIsTab OnKeyPress eseményéhez. A komponens minta készítés egy kicsit emlékeztet az előző fejezetben tárgyalt Object Repoitory használatára. Ez után az alapozás után, a következő fejezetben már valóban elkezdjük a saját komponenseink készítését.
28
Csoportmunka Használd ezután az alábbi formátumokat a kódodban: for i := 0 to 10 do begin ... end; if b = 5 then begin ... end else begin ... end; case feltetel of feltetel: begin ... end; else { case } ... end; Valami1 := TSomeClass.Create; try Valami2 := TSomeClass.Create; try { ... } finally Valami2.Free; end; finally Valami1.Free; end;
Függvények, eljárások A név utaljon az eljárás tartalmára és ne legyen túl hosszú (max. 20 karakter). Amennyiben több szóból tevődik össze, mindegyiket kezdjük nagybetűvel. Lehetőleg ne tartalmazzon aláhúzás jelet. Használjunk magyar neveket. procedure SajatRendezo();
29
A paraméterek neve kis „a”-val kezdődjön, nincs szóköz a függvény neve után, sem az első paraméter előtt: procedure SajatRendezo(aNev: string; aKor: integer); Amennyiben a paraméter nem módosul a metódusban, használjuk a const deklarációt (főleg record, array, ShortString, interface típusoknál). Tegyük előre a (hívó által) gyakrabban használt paramétereket, hátra a kevésbé használtakat, ez optimalizálást tesz lehetővé a fordító számára. Előre tegyük a bemenő utána a kimenő paramétereket.
Kötelező feladatok 1. Keress a számítógép háttértárolóján .bpl kiterjesztésű fájlokat! Mire is jók ezek? Mit tartalmaznak? Milyen fájlok vannak hozzájuk rendelve? 2. Készíts egy egyszerű programot, buildeld! Nézd meg a futtatható .exe állomány mértét a Windows Explorer segítségével! Ezek után változtasd meg a Project Options-ben, hogy a programod runtime komponenscsomagokat használjon. Buildeld újra a programot, és nézd meg most a méretét! 3. Látogass el a http://www.devexpress.com oldalra és nézd meg a komponensfejlesztő környezet és a komponenseket, amelyeket ezzel fejlesztettek. 4. Próbáld kitalálni előző féléves ismereteid alapján, hogy a könyvtárban, ahová az mxCalendár fájljait installáltad milyen fájlok keletkeztek a fordítás és installálás után, és mi a feladatok ezeknek a fájloknak (13. ábra)! 5. A kölcsönző programodhoz készíts egy beléptető képernyőt. Fontos hogy szerepeljen rajta a felhasználó név és a jelszó (amelyek most egyelőre csak egy megadott, és a kódban lekezelt értékek lehetnek). Használj TAzEnterIsTab komponenst, hogy ha valaki entert nyom tab helyett csak a következő kontrollra küldje a Windows. Természetesen, ha a „Rendben” gombon nyom entert valaki, akkor hajtódjon végre a jelszóellenőrzés, a „Mégse” gombra kattintva pedig záródjon be az ablak. Csak akkor nyíljon meg az MDI keretablak, ha a felhasználó jó nevet és jelszót ütött be. Teszteld az alkalmazást!
30
13. ábra
Szorgalmi feladatok 1. Eddigi programozói tapasztalatod alapján találj ki olyan komponenseket, amellyeket ha elkészítenél, valószínűleg más programozók is megvásárolnák! Te milyen komponenseket tudnál legjobban használni a kölcsönző program elkészítése közben?
31
5. Komponensfejlesztés a) Miért szoktak az emberek komponenseket írni? Például azért, mert programozás órán rákényszerítik erre őket. Bár ez most lehet neked hihetetlennek tűnik, de vannak akik kényszer nélkül is képesek ilyesmikre. Kényelmi szempontból a komponenshez csatolt kód azt jelenti, hogy elég egyszerűen rádobni a komponenst egy formra, aztán egy másikra, aztán egy harmadikra és így tovább és megvalósítható ugyanaz a funkció egyetlen sor kód írása nélkül. Hibajavítás vagy módosítás szempontjából elég egyetlen helyen javítani a kódot, majd egy újrafordítás után valamennyi helyen megváltozik a komponens viselkedése (és akkor még nem is beszéltünk az objektum-orientáltságról). Anyagias világunkban nem mellékes az sem, hogy pénzügyi szempontból is megéri komponenst fejleszteni, ugyanis számos olyan cég van, aki képes sok pénzt kifizetni azért, hogy ne kelljen újra feltalálnia a kereket (még ha a beszerzett kerék nem is teljesen kör alakú esetleg). Összefoglalva, ha egy speciális funkciót vagy kontrollt többször is használsz egy alkalmazásban, vagy későbbi programozói munkádban, akkor mindenképpen érdemes komponensként megvalósítanod.
b) Miből célszerű származtatni az új komponenst? • •
•
• •
Hogyha csak egy új funkciót akarsz megvalósítani, akkor használd azt a komponenst ősként, amire az új tulajdonságot érvényesíteni akarod. Ha nem csak új fukciót akarsz hozzáadni egy komponenshez, hanem egy-két régit egyúttal ki is akarsz venni belőle, akkor erre az általános gyakorlat az, hogy létre kell hozni egy komponenst, amelynek minden tulajdonsága, eseménye és metódusa a protected szekcióban kerül deklarálásra. Ha kérdéses komponenst ebből az alaposztályból származtatod, akkor ha valamelyik eredeti tulajdonságot vagy metódust láthatóvá akarod tenni, akkor publikálod, vagyis átrakod a published kulcsszó alá. A trükk tehát, hogy elrejted, ami nem kell, és megjeleníted, amire szükséged van. A TWinControl komponensben került először bevezteésre a Windows kezelő, illetve a fókusz, így minden komponensnek, amire fókusz kerülhet ebből az ősből kell származni, vagy valamelyik utódjából. Ilyen utód pl. a TCustomControl, aminek van egy Canvas propertyje. A TGraphicControl-nak nicsen Windows kezelője, így a fókusz sem kerülhet rá. Például a TLabel komponens is ebből származik. Vannak olyan komponensek, amit nem a GUI (General User Interface) interaktivitás növelése miatt alkalmazunk, hanem egyszerűen csak könnyebbé akarjuk velük tenni az életünket. Amit a TComponent objektumból származtatunk, az csak fejlesztési időben látható (design time ld. 4. fejezet). Tipikusan ilyen komponensek az adatbázis csatlakozásért felelős komponensek, vagy a Timer komponensek.
32
c) TElsoSajatKomponensem Miután eldöntöttük, hogy milyen őst választunk alap objektumként a komponensünk számára, a következő lépés elkészíteni a komponenst. A komponensünk neve TElsoSajatKomponensem lesz, de ez még csak gyakorlás, nem olyan céllal készítjük a komponenst, hogy azt felhasználjuk a Kölcsönző programban. Válasszuk ki a Component → New Component menüpontot (1. ábra).
1. ábra Az Ancestor type mezőbe az ős komponens neve kerül, amiből származtatunk, a Class Name az új komponensünk osztályneve, a Palette Page annak a palettának a neve, amelyen fel fog tűnni az új komponensünk ikonja. Ha ez utóbbiba egy olyan nevet írunk, amilyen paletta még nem létezik, akkor az új palettát elkészíti a Delphi nekünk. Kattintsunk az „Install” gombra, és a megjelenő ablakban (2. ábra) eldönthetjük, hogy egy új csomagba vagy már meglévőbe installáljuk az új komponensünket. Kattintsunk az „Into new package” fülre, majd adjunk meg egy elérési utat és csomag nevet.
2. ábra
33
3. ábra Az „OK” gomb nyomására felugró kérdésre (3. ábra) válaszoljunk „Yes”-szel. Miután újrabuildeltük a csomagot, mentsük el a változásokat. Ezzel elkészítettünk egy új komponenscsomagot, amely tartalmazza az ElsoSajatKomponensem komponenst. A komponensírásban kiemelt fontosságúak a Private, Protected, Public és Published kulcsszavak, ezért röviden ismételjük át őket: • Private – a metódusok, tulajdonságok és események, amelyek ebben a szekcióban kerülnek deklarálásra, csak a komponens unit-ján belül elérhetőek. Ha több komponenst deklarálunk egy unitban, akkor azok hozzáférhetnek egymás privát dolgaihoz. • Protected – az ebben a szekcióban deklarált metódusok, tulajdonságok és események azok az osztályok számára is elérhetőek, amelyeket ebből az osztályból származtatunk. • Public - metódusok, tulajdonságok és események ebből a szekcióból, bárhonnan elérhetőek. • Published – Azok a tulajdonságok vagy események,a melyeket ebben a szekcióban deklarálunk feltűnnek az Object Inspectorban is, tehát tervezési időben értékeket tudunk nekik adni, amelyek a project-el elmentődnek. Írjuk be az új komponensünk unitjának deklarációs részébe a következőket: private { Private declarations } FIndulas, FMegallas : DWord; protected { Protected declarations } function GetFutasiIdo : String; virtual; public { Public declarations } procedure Start; virtual; procedure Stop; virtual; property Indulas : DWord read FIndulas; property Megallas : DWord read FMegallas; property FutasiIdo : String
34
read GetFutasiIdo; published { Published declarations } end; Két változót adtunk meg: FIndulas és FMegallas. Az konvenció, hogy a komponensek létrehozásánál a változó neveket nagy F betűvel kezdjük. Két metódusunk is van, amelyek kontrollálják ezt a két tulajdonságot: Indulas és Megallas. A GetFutasiIdo függvény pedig szöveges formában visszaadja majd FutasiIdo-t (a Get szintén valami konvencióféle, azokra a függvénykre, amelyek valamilyen adatot nyernek ki). Nyomjunk Ctrl+Shift+C-t, hogy a Delphi automatikusan kiegészítse a kód törzs részét. Majd írjuk be az alábbiakat: { TElsoSajatKomponensem } function TElsoSajatKomponensem.GetFutasiIdo: String; begin Result := IntToStr(FMegallas - FIndulas); end; procedure TElsoSajatKomponensem.Start; begin FIndulas := GetTickCount; end; procedure TElsoSajatKomponensem.Stop; begin FMegallas := GetTickCount; end; Mindeközben ne bántsuk a már a Delphi által alapból beírt register eljárást: procedure Register; begin RegisterComponents('SajatKomponensek', [TElsoSajatKomponensem]); end; Fontos lehet megjegyezni, hogy a Delphi 4-es verziójától kezdve a Register eljárást mindig nagy R-rel kell írni a C++ Builderrel való kompatibilitás miatt (a C++ case sensitive). Mentsd el a unitot, nyisd meg újra a package file-t (File, Open Project a menüből majd válaszd ki a "Delphi Package" –t a fájlt típus kombóban). Ha megnyílt a csomag, akkor kattints a „Compile” gombra, majd az „Install”-ra is. Mindezek után felfedezheted a komponensedet a Sajatkomponensek fülön. Az ikonja az alapértelmezett komponens ikon, de ha az egeret fölé húzod, akkor megjelenik a neve is. Ezután teszteljük le a komponensünket! Nyissunk egy új SDI alkalmazást, dobjuk rá a komponenst, majd írjunk hozzá valami hasonló kódot, mint itt alább:
35
procedure TForm1.btnStartClick(Sender: TObject); begin ElsoSajatKomponensem1.Start; end; procedure TForm1.btnStopClick(Sender: TObject); begin ElsoSajatKomponensem1.Stop; Caption := ElsoSajatKomponensem1.ElapsedTime; end; Két gombot is tettünk tehát a formra: a „Start” megnyomásakor elindul az idő számolása, majd a „Stop” megynomásával beíródik az a form címmezőjébe az eltelt idő. Események létrehozása Néhány egyszerű lépésben most adjunk eseményeket a komponensünkhöz. Az események lehetővé teszik, hogy a komponens kommunikáljon alkalmazásunkkal, ha valami említésre méltó dolog történik. Első lépésként hozz létre egy új komponenst TMasodikSajatKomponensem néven, amit az első komponensedből örököltettél, majd installáld a Delphi IDE-be. Ezek után egészítsd ki a deklarációs részt a következőképpen: type TStatusz = (stElindult, stMegallt); TStatuszValtozasEsemeny = procedure (Sender : TObject; Statusz : TStatusz) of object; TMasodikSajatKomponensem = class(TElsoSajatKomponensem) private { Private declarations } FStatusz : TStatusz; FOnIndulas, FOnMegallas : TNotifyEvent; FOnStatuszValtozas : TStatuszValtozasEsemeny; protected { Protected declarations } public { Public declarations } constructor Create(AOwner : TComponent); override; destructor Destroy; override; procedure Start; override; procedure Stop; override; property Statusz : TStatusz read FState; published { Published declarations } property OnIndulas : TNotifyEvent read FOnIndulas write FOnIndulas; property OnStatuszValtozas : TStatuszValtozasEsemeny 36
read FOnStatuszValtozas write FOnStatuszValtozas; property OnMegallas : TNotifyEvent read FOnMegallas write FOnMegallas; end; Az események tehát egyszerű eljárások vagy ritkábban függvények, amelyek az osztályhoz tartoznak. A unit törzsét a következőképpen egészítsük ki: procedure TMasodikSajatKomponensem.Start; begin inherited; //Ez hívja meg a TElsoSajatKomponens örökölt //startját FState := stElindult; if Assigned(OnIndulas) then OnIndulas(Self); if Assigned(OnStatuszValtozas) then OnStatuszValtozas (Self, Statusz); end; procedure TMasodikSajatKomponensem.Stop; begin inherited; // Ez hívja meg a TElsoSajatKomponens.Stop-ot FStatusz := stMegállt; if Assigned(OnMegallas) then OnMegallas (Self); if Assigned(OnStatuszValtozas) then OnStatuszValtozas (Self, Statusz); end; constructor TMasodikSajatKomponensem.Create(AOwner: TComponent); begin inherited; //Itt inicializálódnak a tulajdonságok és jönnek létre a //komponensek FStatusz := stMegallt; end; destructor TMasodikSajatKomponensem.Destroy; begin //Ez elkészült obkjektumok felszabadítása inherited; end; Fordítsd újra a csomagot, majd mentsd el. Teszteld ezt a komponenst is szem előtt tartva, hogy mindhárom deklarált eseményt kipróbáld: • OnIndulás – a státusz stElindult; • OnMegallas – a státusz stMegallt; • OnStatuszValtozas – változik a státusz.
37
A komponenseink nevének elég hosszú nevet adtunk, ezt a feladatokban és a későbbiekben ne kövesd. Hasznos ha egyszerű, de egyedi nevet adsz a komponenseidnek.
d) TskBetuKombo A kölcsönző programunkban az igazolás nyomtatásánál lehetőség lesz formázott üzenetek megjelenítésére (pl. Köszönjük a kölcsönzést!), ehhez egy fontos elem lesz a betűtípus kiválasztó kombinált lista, amelyet még a későbbiekben is használhastz, ezért készítsük el komponensként. Itt az ős legyen a TComboBox, komponens neve legyen skBetuKombo (az sk a Sajatkomponensek csomagra utal). Hozzuk létre a komponenst, fordítsuk újra, mentsük el. A kombó Items tulajdonságát létrehozáskor fel kell töltened a rendszerben található betűtipusok neveivel, és ezt csakis a Create metódusban lehet elvégezni. Viszont a Create metódust a komponensünk örökli a TComboBox-tól, és ezt szeretnénk az új komponens kódjában felülbírálni. Erre szolgál az override direktíva, ahhoz hogy ezt megértsd néhány szót kell szólnunk a osztályok polimorfizmusáról. Az Object Pascalban léteznek virtuális (virtual) és dinamikus (dynamic) metódusok, amelyek átdefiniálják az alaposztály megegyező nevű metódusát. A program futása közben dől el, hogy végül melyik metódust kell aktivizálni (késői kötés). A tárolási mechanizmusnak köszönhetően a virtuális metódusok a VMT (Virtual Method Table) adott sorszámú bejegyzése alapján aktivizálódnak, míg a meghívandó dinamikus metódus belépési címét egy rendszerrutin keresi meg a dinamikus metódustáblák láncában. A virtuális metódusok hívása gyorsabb, de több memóriát foglalnak, míg a dinamikus metódusoknál éppen fordított a helyzet. Az osztályhierarchia tetszőleges pontján virtuálissá vagy dinamikussá tehető bármely metódus, azonban a továbbiakban minden ebből származtatott osztályban használnunk kell az override direktívát, ha a metódust újradefiniáljuk. A másik gond, amivel a komponensünk tervezésekor szembesülünk, hogy a Create metódusban még nem látjuk az Items propertyt, mivel az a Parent tulajdonság értékének beállításakor élesítődik, és ezt nem a konstruktor teszi meg, csak később kerül rá sor. Van viszont egy CreateWnd eljárás, ami létrehozza az ablakot, és ebben már van Parent tulajdonsága, azaz ablakleírója is van, tehát hozzárendelhetjük az Items propertyhez a szükséges elemeket. Tehát a unit valahogy így néz ki: unit skBetuKombo; interface uses Windows, Messages, Forms, Dialogs, StdCtrls;
SysUtils,
Classes,
Graphics,
Controls,
type TskBetuKombo = class(TComboBox) public { Public declarations } constructor Create (AOwner: TComponent); override; procedure CreateWnd; override; published
38
{ Published declarations } property Style default csDropDownList; property Items stored False; end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TskBetuKombo]); end; { TskBetuKombo } constructor TskBetuKombo.Create(AOwner: TComponent); begin inherited Create (AOwner); Style := csDropDownList; end; procedure TskBetuKombo.CreateWnd; begin inherited CretaeWnd; Items.Assign(Screen.Fonts); end; end. Természetesen a Create metódus felülbírálását akár ki is lehetne hagyni, ha a Style megadását is áttennénk a CreateWnd metódusba. Kicsit furcsának tűnik, hogy a komponens Style tulajdonságát két helyen is beállítottuk: a deklarációnál a default kulcsszóval, míg a Create metódusban adtunk neki egy értéket. A dolog nem túlbuzgóság eredménye, mivel a default-nak nincsen hatása a tulajdonság kezdeti értékére. Akkor meg mire jó? Az alapértelmezett értékkel bíró tulajdonságok nem szerepelnek a from leírásában, és a dfm fájlban sem. A dolog értelme tehát „csupán” annyi, hogy csökkenni fog a dfm, és ezáltal a futtatható állomány mérete. Az Items körüli bűvészkedésre is hasonló okok miatt van szükség. A property Items stored False; sor arra utasítja a fordítót, hogy a dfm fájlba ne tegye be az Items propertyt. Ha betenné, akkor az teljesen fölösleges lenne, hiszen a komponens ablaka úgyis mindig létrejön, és a tulajdonság a Crate metódusban úgyis midnig felülbírálódna. Egy bonyolult alkalmazásnál akár több tucat komponenscsomag is használható. Ez nagyon megnehezíti a projekt lefordítását, ha például egy új ember csatlakozik a fejlesztésbe (vagy újrainstalláljuk a Windows rendszerünket). Erre találta ki a Borland a csomaggyűjtemény-szerkesztőt (Package Collection Editor), amely indító állomány (pce.exe) a Delphi/Bin könyvtárban található. Ennek segítségével könnyen lehet egyben kezelni több komponenscsomagot, így a telepítés is egyszerűbbé válik.
39
Csoportmunka Változók használata Természetesen olyan neveket adjunk a változóknak, amelyek utalnak a tartalmukra. Logikai változóknál derüljön ki mi az elvárt érték. Ciklusváltozóknak nyugodtan használjuk a jól megszokott i, j, k neveket. Lokális (eljáráson belül deklarált) változóknál is használjuk a nagybetűs kezdést minden benne szereplő szónál: var EnStringValtozom:
string;
A globális (Unit szintű) változókat kezdjük „g”-vel, az osztály tagváltozóit „i”-vel (instance variable). gFelhasznaloNeve: string; iFelirat: string; Komponens típusú változókat kezdjünk max. 3 kisbetűvel, ami utal a típusára: edNev: TEdit; qKeszlet: TIBQuery; tblRaktar: TIBTable; frmKeret: TKeretForm; rptCegek: TSajatreportForm; Néhány példa a Delphi beépített komponensei esetén: Standard Tab
Additional Tab
Prefix
Komponens
Prefix
Komponens
mm
TMainMenu
bbtn
TBitBtn
pm
TPopupMenu
sb
TSpeedButton
mmi
TMainMenuItem
me
TMaskEdit
pmi
TPopupMenuItem
sg
TStringGrid
lbl
TLabel
dg
TDrawGrid
ed
TEdit
img
TImage
mem
TMemo
shp
TShape
btn
TButton
bvl
TBevel
chb
TCheckBox
sbx
TScrollBox
rb
TRadioButton
clb
TCheckListbox
lb
TListBox
spl
TSplitter
cb
TComboBox
stx
TStaticText
scb
TScrollBar
cht
gb
TGroupBox
rg
TRadioGroup
pnl
TPanel
cl
TCommandList
TChart
40
Data Controls Tab
Win32 Tab Prefix
Komponens
Prefix
Komponens
tbc
TTabControl
dbg
TDBGrid
pgc
TPageControl
Dbn
TDBNavigator
il
TImageList
Dbt
TDBText
re
TRichEdit
dbe
TDBEdit
tbr
TTrackBar
dbm
TDBMemo
prb
TProgressBar
dbi
TDBImage
ud
TUpDown
dblb
TDBListBox
hk
THotKey
dbcb
TDBComboBox
ani
TAnimate
dbch
TDBCheckBox
dtp
TDateTimePicker
dbrg
TDBRadioGroup
tv
TTreeView
dbll
TDBLookupListBox
lv
TListView
dblc
TDBLookupComboBox
hdr
THeaderControl
dbre
TDBRichEdit
stb
TStatusBar
dbcg
TDBCtrlGrid
tlb
TToolBar
dbch
TDBChart
clb
TCoolBar
Saját összetett típusokat kezdjük „T-vel a rá mutató pointert „P”-vel: type PSajatTomb = ^TSajatTomb; TSajatTomb = array[1..100] of Integer; type PDolgozo = ^TDolgozo; TDolgozo = record Nev: string; Fizetes: Double; end; TNewClass = class(TObject) private iSzamla: Integer; iOsszeg: Integer; end;
Kötelező feladatok 1. Szánj annyi időt a kódra, hogy átböngészed az elnevezési konvenciók szempontjából is. Mire utal pl. az st előtag, vagy az On előtag az események megadásánál? Miért célszerű használni őket? 2. Használd a betűtípus kiválasztó listánkat egy egyszerű alkalmazásban! 3. Készíts URL TLabel komponenst! A komponens feladata, hogy ha a felhasználó rákattint, akkor automatikusan elindul az alapérztelmezett
41
böngésző és megynílik egy a tulajdonságok között megadható link. A linknek más a színe, ha az egérrel fölötte tartózkodunk, illetve amikor nem. A származtatás történjen a TCustomLabel komponensből, a link megnyitása a HlinkNavigateString(nil, c) függvényhívással lehetséges, ahol c az URL WideChar típusban. 4. Olvasd el még egyszer az előző 4. fejezetet, majd újra ezt az 5. fejezetet!
Szorgalmi feladatok 1. Írj egy új eseményt a betűtipus kiválasztó listának! 2. Nézz utána, hogyan lehet bitképet hozzárendelni az újonnan létrehozott komponenshez! 3. Írj egy olyan kombó komponenst, amely Hintben megjeleníti a kiválasztott szöveget, de csak akkor, ha a kiválasztott szöveg nem fér ki a kombóbox ablakába, vagyis lemarad a végéről legalább egy karakter! 4. Írj egy skStatusBar komponenst, amely megjeleníti az időt, a dátumot, és még egy részében plusz infó megjeleníthető (4. ábra)!
4. ábra
42
6. Adatbáziskezelés alapjai a) Az adatbáziskezelés fogalma Mint ahogyan azt az adatbáziskezelés tárgyból megtanulhattad, a relációs adatbázisokat adattáblák alkotják, amelyek között különböző kapcsolatokat, relációkat tudunk definiálni. Az adattábla létrehozásakor meghatározzuk a mezők (oszlopok) számát, de a rekordok (sorok) számára semmilyen megkötés nincs. Egy táblán belül a rekordok szerkezete azonos, amit a mezők adnak meg nekünk. Az adatbáziskezelő szoftver arra szolgál, hogy segítségével kinyerhetjük az adatokat az adatbázisból, és különböző műveleteket végezhetünk az adatokon segítségével. Az adatbázis-feldolgozás során a tábla rekordjait, vagy egy rekord egy bizonyos mezőjét érjük el. Az adatkezelés végrehajtása során egy adott táblában mindig egy adott rekord az aktuális, amire az ún. rekordmutató mutat. Az adatbáziskezelés a Delphiben egy nagyon szokványos feladat, a programozók legnagyobb része – főleg Magyarországon – arra használja a Delphit, hogy üzleti alkalmazásokat írjon vele, amely adatbázisban tárolja az adatait.
b) Programozási környezetünk frissítése Ahhoz, hogy nekikezdjünk az adatbáziskezelésnek adatbáziskezelőre van szükségünk, ahhoz hogy meg tudjuk „szólítani” az adatáziskezelőt pedig, olyan komponens(ek)re, amelyek segítségével kapcsolódni tudunk az adatbáziskezelőhöz. Az adatbáziskezelők választéka elég nagy, de a legnagyobb teljesítményű szabadon forráskódú (open source) rendszer kétségkívül a Firebird 1.5-ös verziója. Hogy a Firebird-öt használni tudjuk, először távolítsuk el az Interbaset a gépünkről. A két adatbáziskezelő eszköz azonos tőről fakad, ezért megzavarják egymás működését. Bár van lehetőség a két program párhuzamos használatára, de mi most ezzel az esettel nem foglalkozunk. A Vezérlőpult „Programok telepítése és eltávolítása” nevű programjával távolítsuk el az Interbase adatbáziskezelőt (1. ábra). .
1. ábra
43
Az alábbiakban tárgyalandó frissítéseket és programokat a http://www.agr.unideb.hu/~salga/oktatas.html oldalról töltheted le. Az installálás első lépéseként indítsuk el a „Firebird-1.5.1.4481-Win32” állományt (2. ábra).
2. ábra Kövessük végig az installálás lépéseit. Célszerű azt választani, hogy a szerver szervízként fusson a szerverünkön (ekkor nem rendelődik hozzá alkalmazás ikon a futása során), és hogy automatikusan elinduljon (így nem kell a szerverindítással foglalkoznunk, viszont mindig elindul a szerver és foglal egy kis memóriát). Ezeket a beállításokat később is változtathatjuk a Vezérlőpulton (2. ábra).
3. ábra 44
Az új adatbáziskezelőnkhöz fel kell installálni a komponensfrsissítéseket, amelyek a Firebird közvetlen elérését szolgálják (IBX komponenscsomag). Ezt csak kétszerre tudjuk megcsinálni: indítsd el először az „IBXDP452Upd.EXE” állományt, majd a sikeres frissítés után az „IBXDP504Upd.EXE” –t is. Ahhoz, hogy az adatokat ne csak a Delphin keresztül tudjuk elérni szükségünk lesz az IBOConsole nevű szabad forráskódú alkalmazásra, amit insatllálni se kell, csak bemásolni egy könyvtárba. Ezután tegyünk egy linket az asztalra hozzá, mivel gyakran fogjuk használni a későbbiekben. Ha már így belemelegedtünk a frissítéséekbe, és mivel a későbbiek folyamán megismerkedünk majd a kimutatások készítésével is, fontos, hogy a Quickreport frissítését is telepítsük (qr35sd5.exe). És a Delphi kiadott a D5-höz egy úgynevezett patch fájlt, vagyis foltozást. Ha még nincs fenn a gépeden, akkor ezt is telepítsd mindenképpen (D5EntUpdate.exe).
c) BDE A számítógép az adatokat és az adatbázisokat fájlokban tárolja. Az adatbáziskezelőtől függ az, hogy milyen módon történik a tárolás. Történhet egyetlen fájlban is, de felbontható az adatbázis sok kis fájl rendszerére, amelyek egyenként tartalmazzák az egyes táblákat, indexeket vagy más összetevőket. A Paradox és dBase adatbázisokat egy megadott könyvtárban találjuk, ahol az egyes táblák fájlokban tárolódnak. Ezzel szemben az Access, Interbase, Firebird és a modern adatbáziskezelő eszközök az egész adatbázist egyetlen állományban tárolják (ha a fájl túl nagy lesz a sok adat bevitele során, a legtöbb adatbáziskezelő támogatja az adatbázis szétosztását több fájlba, de ennek a darabolásnak az elve egészen más, mint pl. a Paradoxnál). A Delphi adatbázis-alkalmazások nem közvetlenül kommunikálnak az adatforrásokkal, közvetlenül csak ritkán tudják kezelni az adatbázisfájlokat. Ezért kell egy adatbázismotor, mint például a Borland Database Engine (BDE) vagy a Microsoft ActiveX Data Objects (ADO). Ezzel szemben az InterBase Express (IBX) egy olyan komponensgyűjtemény (amit ez imént frissítettél), amely segítségével az InterBase és a Firebird adatbázisok közvetlenül érhetőek el. Az ezekkel készült alkalmazások jobban és gyorsabban dolgoznak, jobban kihasználják a két adatbáziskezelő lehetőségeit. A dolog egyetlen hátránya, hogy ha egy másik adatbáziskezelőt szeretnénk az alakalmazásunk „alá” rakni, akkor ahhoz jelentősen át kell írni alkalmazásunkat. Mielőtt belekezdenénk egy alkalmazás elkészítésébe, mérlegelnünk kell ezért, hogy milyen jellegű adatbázismotort fogunk alkalmazni. A kölcsönző programunkban mi az IBX-et fogjuk használni, mert a FireBird tökéletesen megfelel a feladatra, hiszen multinacionális nagyvállalati igényeket is kielégít. A BDE viszont sokkal fontosabb témakör, minthogy egyetlen bekezdéssel elintézhessük, ezért a fejezet első részében egy példaalkalmazást tárgyalunk, ami BDE segítségével éri el a Borland által a Delphivel együtt rendelkezésünkre bocsátott egyik példa adatbázist. A BDE segítségével az InterBase és a FireBird is elérhető természetesen és egy nagyon jül sikerült modul. Kifinomult gyorsítótárazás és heterogén táblaegyesítés tulajdonságait például nem találhatjuk meg a Microsoft-os konkurrensben az ADOban sem. A Microsoft az ADO vagy az ODBC (Open Database Connectivity) adatbázismotorok segítségével biztosít hozzáférést az adatokhoz, levelezési 45
fájlrendszerekhez, office dokumentumokhoz, egyedi üzleti objektumokhoz. Az ODBC-t minden 32 bites Windows operációs rendszer tartalmazza, míg az ADO a Windows 2000 operációs rendszerrel került be az elérhető adatmotorok közé. A BDE-t ezzel szemben mindig telepíteni kell arra a gépre, ahol a BDE-s alkalmazásunkat használni akarjuk. Az InstallShield segjtségével könnyen készíthetünk olyan telepítő állományokat, amelyek automatikusan beállítják a BDE-t a kliens gépeken és a BDE szabadon terjeszthető. A BDE (más adatbázismotorokhoz hasonló módon) alias-ok (álnevek) használatával hivatkozik az adatbázisfájlokra vagy könyvtárakra. Az adatbázisok számára új aliast a DataBase Explorer vagy a Database Engine Configuration segédprogramok segítségével adhatjuk meg.
4. ábra A 4. ábra és az 5. ábra két különböző szemléletmóddal ábrázolja az adatelérést a Delphi 5-ből. Viszont a lényeg mindkettőben ugyanaz: gyakorlatilag bármelyik adatbáziskezelő által kezelt adatbázis elérhető a Delphiben fejlesztett alkalmazásainkból. Hosszú, göröngyös út vezet oda, hogy megtudd melyik adatbázismotor a legmegfelelőbb neked, de kezdetnek a BDE egy nagyon jó választás lehet.
46
Delphi alkalmazás
BDE ODBC FoxPro
Access
Paradox
dBase
ASCII
Access
Interbase, Firebird
Excel Oracle
SQL Links Interbase, Firebird
DB2
SyBase
SQL szerver
Informix
Oracle
5. ábra
d) Adatelérés-komponensek Az adatelérési komponenseket könnyen felfedezhetjük a komponens-paletta Data Access lapján (6. ábra).
6. ábra Bár majd beszélni fogunk a Table komponens hibáiról, a legegyszerűbb mód, hogy adatokhoz hozzáférjünk Delphiben, a Table komponens használata. A TTable objektumok egy adattáblára hivatkoznak, és nagyon egyszerűen beállíthatóak. Ha ezt a komponenst használjuk, akkor egy alias vagy az adatbázist atrtalmazó könyvtár elérési útvonalának megadásával meg kell határoznunk az elérni kívánt adatbázist a DatabaseName propertyben. A TableName tulajdonság a tábla neve, ami az adatbázis helyes beállítása után már kiválasztható a legördülő menüből. A TQuery komponens sokkal jobban testreszabható adatelérést biztosít nekünk, ugyanis az SQL tulajdonságban tetszőleges SQL utasítást megadhatunk. Tehát a Query segítségével akár több táblából is kinyerhetjük egyszerre az adatokat és ráadásul a teljesítménye is jobb a komponensnek, az egyetlen gond vele, hogy amikor menteni is akarunk, akkor kicsit macerás a beállítása. A BDE-ben javasolt adatelérés tehát: ha adatot is kívánunk menteni a táblába, akkor használjuk a Table komponenst, az adott tábla adatainak elérésére, ha csak megjeleníteni kívánunk adatokat, és biztosan nem lesz adatmentés, akkor használjuk a Query komponenst. Az adatelérés-komponensek mellé felrakhatunk egy TDatabase komponest is, aminek a tranzakciókezelésben van inkább szerepe (erről majd a következő fejezetben tenulunk bővebben).
47
Minden esetben szükség van azonban – feltéve, hogy az adatokat vizuális komponessel meg is akarjuk jeleníteni -, egy TDataSource komponensre, aminek segítségével a Query vagy Table komponenseinket össze tudjuk kapcsolni a megjelenítő komponensekkel. Ez egy kevés tulajdonsággal bíró, de szemléletben nagyon fontos objektum. Az adatforrás komponense biztosítja ugyanis a szétválaszthatóságot a vizuális és az adatelérési komponensek között. Ennek azért van nagy jelentősége, mert mint ahogyan azt már említettük, az adatbázsikezelő gyakran változhat egy adott allkalmazás esetében, viszont a felületet így nem kell megváltoztatnunk egy másik adatbázisra váltás esetén. A szétválaszthatóságot az is jól mutatja, hogy nagyobb alkalmazásokban sok fejlesztő az adatelérési komponenseket (a DataSource komponessel együtt) egy adatmodulra helyezi. Új adatmodult a File -> New -> New Page: Data Module kiválasztásával hozhatunk létre. Fontos még megemlítenünk az adatkészletek állapotát (State property), ez a következő értékeket veheti fel: • dsBrowse – csak megtekintjük az adatokat, böngészünk közöttük; • dsEdit – Edit metódus meghívásával kerül ebbe az állapotba a DataSet, ilyenkor szerkeszthetjük az adatokat. Ha a DataSource AutoEdit propertyje True, akkor a vizuális komponensben minden kódolás nélkül beleszerkeszthetünk a táblába. Az adatok mentéséhez szükséges a Post kiadása; • dsInsert – Append vagy Insert metódus hatására kerül ebbe az állapotba a DataSet. Ilyenkor új rekordot szúrhatunk be. Itt is fontos a Post kiadása; • dsInactive – nem hozzáférhető a DataSet; • dsSetKey – keresés előkészítésekor; • dsCalcFileds – amikor számított mező értékeit számolja a DataSet; • dsFilter – szűrő beállításakor; • dsNewValue, dsOldValue, dsCurValue – ideiglenes tároló frissítéskor.
e) Egy egyszerű BDE-alkalmazás Egy új alkalmazás formjára tegyünk egy Table egy Query és egy DataSource komponenst (7. ábra).
48
7. ábra Mind a Table, mind pedig a Query komponens DatabaseName propertyjét állítsuk a „DBDEMOS” aliasra, amely a Borland példaadatbázisa (Paradox formátumú) és a telepítéskor jön létre a gépen. Ezután a Table komponens TableName tulajdonságának állítsuk be a „country.db” táblát. Ez tartalmazza az „Újvilág” államait és azok adatait. Ha a Table Active tulajdonságát True-ra állítjuk, akkor meg is nyitottuk az adatokat. A Query-nél a DataBaseName tulajdonsággal hasonlóan járjunk el, mint azt a Table-nél tettük, majd az SQL property értékének adjuk a következő szöveget: Select * from Country Talán mondani sem kell, hogy ez az SQL egyenértékű a Table komponensben megadott adathalmazzal. Váltsuk a Query Active tulajdonságát True-ra, ami ha helyesen írtuk be a select-tet, akkor gond nélkül lezajlik. A DataSource komponens DataSet tulajdonságát tetszőlegesen állíthatjuk a Table vagy a Query komponensünkre. Ezután a Data Controls lapról (8. ábra), tegyünk egy DBGrid komponenst a formunkra, aminek állítsuk az Align propertyjét alClient-re és a DataSource-nak adjuk meg a formon lévő TDataSource komponens nevét.
8. ábra Ebben a pillanatban már design time is megjelenennek az adatok a gridben, de ha futtatjuk az alkalmazásunkat, akkor láthatjuk, hogy egy táblázatos adatnézegető alkalmazást kaptunk.
49
7. Adatbáziskezelés Interbase Express komponensekkel a) Az adatbázis elkészítése Első lépésként készítsük el a kölcsönző programunk adatbázisát. Nyissuk meg az IBOConsole-t. Regisztráljunk egy lokális adatbázis szervert: Server → Register, Local Server rádiógomb, ilyenkor a saját gépünkön érjük el az adatbázist. Lépjünk be a Firebird (és az Interbase) alapértelmezett felhasználójaként. Felhasználói név: SYSDBA, jelszó: masterkey (1. ábra).
1. ábra Belépés után létre kell hoznunk egy olyan felhasználót, amellyel létrehozzuk majd az adatbázist, és akinek rendszergazdai jogot adunk, hogy bármit tudjon majd módosítani. Erre azért van szükség, mert a SYSDBA account közismert, és ha ezzel a felhasználóval dolgoznánk, bárki hozzáférhetne az adatokhoz, és az adatbázist is kénye-kedve szerint módosíthatná.
2. ábra
50
A jobboldali panelen válasszuk a „User Security” menüpontot (2. ábra), és vegyük fel a DEVELOPER felhasználót, a jelszó legyen „nyuszi” (vigyázat a jelszó case sensitive) (3. ábra).
3. ábra Zárjuk be az IBOConsole-t (vagy jobb egérkattintás a Local Server-en és logout), majd újra nyissuk meg, és már DEVELOPER-ként lépjünk be. Válaszzuk a „Database” menü „Create Database” menüpontját, majd hozzunk létre valahol a lokális háttértárunkon egy „kolcsonzo.fdb” adatbázist, legyen az alapértelmezett caracter set WIN1250, és az aliasnévre is írjunk valamit (ezen a néven érhetjük majd el az adatbázist az IBOConsoleban) (4. ábra).
4. ábra
51
Normál esetben ilyenkor érdemes kitörölni a SYSDBA usert, de most ezt ne tegyük meg. Hozzunk létre néhány fontos táblát, azután belekezdhetünk a Delphi programozásba. A táblák létrehozásához az alábbi scriptet kell lefuttani a Tools → Interactive SQL ablakban (5. ábra), végrehajtás CTRL+e: /* Ez a tábla fogja majd a felhasználói neveket és jelszavakat tárolni. Az ID_USER mező minden táblában benne lesz, szerepe, hogy beleírjuk minden rekord után, hogy ki hozta létre, illetve ki módosította utoljára az adott rekordot */ CREATE TABLE LOGIN ( ID INTEGER NOT NULL, FELHASZNALO_NEV VARCHAR(20) CHARACTER SET WIN1250 NOT NULL, JELSZO VARCHAR(20) CHARACTER SET WIN1250, BELEPETT CHAR default 'F', ID_USER INTEGER, TOROLVE CHAR default 'F', CONSTRAINT LOGINPK1 PRIMARY KEY ( ID ) ); /* Egyedi kulcsot készítünk, ami arra szolgál, hogy ugyanolyan néven ne lehessen két felhasználüt felvenni */ CREATE UNIQUE INDEX );
LOGINNEVIDX
ON
LOGIN ( FELHASZNALO_NEV
/* A KESZLET táblában fogjuk tárolni azokat az elemeket, amelyeket kölcsönözni lehet. Most legyenek ezek a kölcsönzendő dolgok videókazetták. Ezekhez speciális mezőket kell majd beállítanunk, de mindig tartsuk majd szem előtt, hogy általános célú kölcsönzőprogramot szeretnénk írni, amit kis változtatással el tudunk majd adni pl. a kisgépkölcsönző vállalkozásoknak is */ CREATE TABLE KESZLET ( ID INTEGER NOT NULL, NEV VARCHAR(75) NOT NULL, NYILVANTARTASI_AR numeric(15,4), VONALKOD VARCHAR(25) CHARACTER SET WIN1250, SAJATKOD VARCHAR(25) CHARACTER SET WIN1250, MINIMALIS_KESZLET INTEGER NOT NULL, LEIRAS VARCHAR(200) CHARACTER SET WIN1250 , ID_USER INTEGER, BESZERZESI_DATUM DATE, MEGSZUNT CHAR default 'F', 52
ELFEKVOSEG_IDEJE CONSTRAINT KESZLETPK1 );
INTEGER DEFAULT 3, PRIMARY KEY ( ID )
/* Két videó címe megegyezhet, de kell valami azonosító kulcs amit publikussá teszünk (az ID-t nem fogja a felhasználó látni), erre a célra megfelel a SAJATKOD mező */ CREATE UNIQUE INDEX
SAJATKOD
ON
KESZLET ( SAJATKOD );
/* Mivel az ID_USER mező egy külső kulcs, definiálnunk kell rá a megfelelő kényszert */ ALTER TABLE KESZLET ADD CONSTRAINT KESZLETLOGINFKEY1 FOREIGN KEY (ID_USER) REFERENCES LOGIN (ID) ON UPDATE CASCADE; /* A vevők táblában rögzítjük majd a vevők adatait */ CREATE TABLE VEVOK ( ID INTEGER NOT NULL, NEV varchar(50) NOT NULL, IRSZAM VARCHAR(30) CHARACTER SET WIN1250, VAROS VARCHAR(50) CHARACTER SET WIN1250, UTCA_HAZSZAM VARCHAR(75) CHARACTER SET WIN1250, MEGJEGYZES VARCHAR(150) CHARACTER SET WIN1250, ID_USER INTEGER, TELEFONSZAM VARCHAR(50) CHARACTER SET WIN1250, MEGSZUNT CHAR default 'F', MINOSITES CHAR(1) CHARACTER SET WIN1250, EMAIL VARCHAR(50) CHARACTER SET WIN1250, SZEM_IG_SZAM VARCHAR(50) CHARACTER SET WIN1250, CONSTRAINT VEVOKPK1 PRIMARY KEY ( ID ) ); /* Egyelőre nem teszünk egyedi indexet (az elsődlegesen kívül erre a táblára, viszont ide is kell a constraint a login táblára */ ALTER TABLE VEVOK ADD CONSTRAINT VEVOKLOGINFKEY1 FOREIGN KEY (ID_USER) REFERENCES LOGIN (ID) ON UPDATE CASCADE;
53
5. ábra Amire még feltétlenül szükségünk van azok a generátorok, amelyek majd minden táblának az ID értékét fogják megadni. Ezek létrehozásához a következő szkriptet kell lefuttatni: /* generáror a LOGIN táblára */ CREATE GENERATOR LOGIN_ID_GEN; /* generáror a VEVOK táblára */ CREATE GENERATOR VEVOK_ID_GEN; /* generáror a KESZLET táblára */ CREATE GENERATOR KESZLET_ID_GEN; Miután az Interactive SQL ablakban lefuttattuk a fenti szkriptet, a generátorok között megjelenik a három generátorunk (6. ábra).
54
6. ábra
b) IBX adatelérés Delphiből Mint arról már az előzőekben is volt szó az adatbáziskezelés szorosan összekapcsolódik a programozással, és fogunk még a későbbiekben is módosítgatni az adatbázison, de most térjünk vissza a Delphihez, és kezdjük el a Kölcsönző programunk fejlesztését. Eslő lépésklént nyissuk meg az MDI projektet, amit a 2. fejezetben készítettünk el (ha nem találjuk, akkor gyorsan hozzunk létre egy új MDI projektet). A „File” menü „New Form” menüpontjával hozzunk létre egy új ablakot a projektünkben, és hozzunk létre egy adatmodult is, amit az Object Repositoryból kell kikeresnünk (7. ábra)
7. ábra 55
Az első feladatunk, hogy érjük el az adatbázist, ehhez rá kell pakolnunk az adatelérési komponenseinket az adatmodulunkra. Ellentétben a BDE alkalmazással itt az Interbase Express (IBX) komponenscsomag elemeit kell használnunk. Az első lépés, mint azt a BDE-nél is láthattuk, az adatbázis elérése, ehhez az IBDatabase komponenst használjuk. Ha duplán kattintunk a komponensre, akkor feljöna szerkesztője, amibe be kell állítani az adatbázsi eléréshez szükséges aparmétereket (8. ábra).
8. ábra A Browse gomb használatánál arra kell ügyelnünk, hogy alapértelmezésben az IBX Interbase adatbázist keres, amelyek *.gdb kieterjesztésűek, tehát át kell állítanunk a fájlok megjelenítését *.*-ra, hogy a kolcsonzo.fdb fájlunk feltűnjön a fájlok között. Figyeljük meg, hogy a „Login Prompt” jelölőnégyzet ki van kapcsolva. Ezt azért tesszük meg, mert az alkalmazásba lépést mi kívánjuk majd leprogramozni, és nem hagyjuk rá a adatbáziskezelőre! Írjuk át az adatbázis-kompnens nevét pl. DB_kolcsonzore, hogy konnyebben tudjunk később rá hivatkozni. Nagy szükségünk van még egy tranzakció komponensre (a tranzakciókról a következő fejezetben olvashatunk), ez az IBTransaction objektum. Kattintsunk duplán erre is, és állítsuk be a tranzakció típusát „Read committed”-re. Ekkor a settings ablakban megjelennek a beállítások (9. ábra). Hogy ezek mit jelentenek, arra most nem térünk ki, viszont az egyik faladat majd ehhez fog kapcsolódni, és általánosságban elmondható, hogy ha többfelhasználós, helyi hálózaton működő alkalmazást akarunk fejleszteni, akkor érdemes ezt az opciót használni.
56
9. ábra Ha ezzel megvagyunk, akkor adjunk értelmes nevet a tranzakciónak (pl. Trans_kolcsonzo), és hivatkozzunk a tranzakció Default_Database tulajdonságából az adatbázsi komponensre, illetve az adatbázis komponens Deafult_Transaction tulajdonságában a tranzakció komponensünkre. Nem árt még az adatmodult is átnevezni (pl. Fo_adatmodul névre). Ha az Active propertiket az adatbázis és a Tranzakció komponensben is True-ra állítjuk, akkor máris hozzácsatlakoztunk aza adtbázishoz, bár ez szemmel látható változást alig eredményez az adatmodulunkon (10. ábra).
10. ábra Kezdjük a KESZLET tábla elérésével a munkát. Először el kell döntenünk, hogy mit akarunk majd a táblával csinálni. Ha csak olvasni akarjuk az adatokat belőle, akkor egészen másképpen kell eljárnunk, mintha egy valódi interaktív kapcsolatot szeretnénk megvalósítani, ahol nem csak olvasni, de írni, törölni vagy éppen
57
módosítani is akarjuk a tábla adatait. Már a BDE-nel is volt szó arról, hogy az előbbi esetben a legegyszerűbb egy TQuery használata (amit az IBX panelen TIBQuerynek hívnak), míg az utóbbi esetben az IBDataSet használata ajánlott. Bár van IBTable komponens is (aminek a használata egyszerűbb és hasonlít a BDE TTable komponensére), de sok kellemetlen tapasztalattól kíméljük meg magunkat, ha a kicsit nehézkesebb kezelésű, de sokkal strapabíróbb TIBDataSet-et használjuk. Mivel a KESZLET tábla adatait mi ebben az esetben szerkeszteni is akarjuk, ezért dobjunk rá az adatmodulra egy IBDataSet-et és nevezzük el tblKeszlet-nek (a ds prefixet majd a datasource-ra használjuk, és mivel úgyse használunk table komponenst, nincs mivel összekeverni a tbl prefixet). Bár nicsen ilyen szabály, mégis igyekezzünk minden ablakunknak létrehozni egy külön TIBTransaction komponenst, mivel így rendezettebben kezelhetjük az adatmentéseinket. Tegyünk fel tehát a tblKeszlet mellé egy transKeszlet komponenst is. Állítsuk be ezt is Read Committed-re, és a Default Database property legyen a DB_Kolcsonzo. Ezután ha a tblKeszlet Default Transaction propertyjét beállítjuk transKeszlet-re, akkor a Default Database tulajdonság is automatikusan beíródik. Ezután adjuk meg a tblKeszlet SelectSQL tulajdonságát: select * from keszlet order by nev Ezt a tulajdonság melletti eclipse (…) gombra kattintva CommandText Editorban a legkényelmesebb megtenni (11. ábra).
megjelenő
11. ábra És most jön a trükk: ha ellenőrizted, hogy csatlakozva vagy-e az adatbázishoz, akkor jobb egérkattintás a tblKeszlet-en, a menüből válaszd ki a „DataSet Editor…” menüpontot, és a megjelenő ablakban minden egyéb fontos SQL-t beállíthaunk. A bal oldali listában állítsuk be a kulcs mezőt, ami az esetünkben az ID, a másik listában hagyjunk mindent kijelölve, hiszen minden mezőt akarunk módosítani (12. ábra). A „Generate SQL” gombra kattintva a komponens kitölti nekünk automatikusan
58
a ModifySQL, InsertSQL, DeleteSQL és RefreshSQL tulajdonságokat. A „Quote Identifier” jelölőnégyzet azt jelenti, hogy tegye-e a mezőneveket idézőjelbe az SQL megalkotáskor. Ezt akkor célszerű használni, ha olyan lehetetlen mezőneveket adott valaki amelyeket másként nem értene a Firebird, egy tipikus példa erre a „selejt%”, mint mezőnév, amitől minden jószándékú programozóknak égnek áll a haja, de az adatbázistervezőktől nagyobb galádság is kitelik.
12. ábra Na még azért nem vagyunk egészen kész az SQL gyártással, ugyanis a D5 IBX egy kis bugja, hogy a RefreshSQL automatikus generálását elvéti. Ugyanis ha „select *”-ot írunk a SelectSQL tulajdonságba, akkor az automatikusan képzett RefreshSQL többnyire a következő: Select from keszlet where ID = :ID Ezt úgy tudjuk meggyógyítani, hogy a select után beírunk egy *-ot. De a hiba nem mindig jelentkezik, van amikor jó az SQL (13. ábra).
59
13. ábra A tblKeszlet GeneratorField tuéajdonságát is be kell állítani. Az ismert eclipsre kattintva egy beállító ablak jelenik meg (14. ábra), ahol meg kell adnunk a generátor nevét, a mezőt, amit állítani akarunk vele, és nagyon fontos, hogy mikor történik az azonosító adása.
14. ábra Mivel mi majd sok esetben Post-olás, vagyis az adatbázisba továbbítás előtt is szeretnénk majd használni az ID értékét, ezért az első rádiógombot jelöljük ki, vagyis minden új rekor emelésekor rendelődjön hozzá a generátorból az új ID a mezőhöz. Ha duplán kattintunk a tblKeszlet komponensre, akkor megjelenik a „Fields editor” ablak, ahol mezőkat adhatunk hozzá a táblához. A szelektált mezők hozzáadása egyszerű: jobb egérkattintás, „Add all fields” (15. ábra). A mezők felvételének a fields editorba, csak akkor van értelme, ha calculated vagy lookup mezőt akarunk definiálni, vagy a mezők egy eseményére akarunk írni programot,
60
egyébként ne használjuk. A későbbiekben látni fogunk példát a Fields Editor használatára.
15. ábra Ha ezekkel készen vagyunk a tblKeszlet Active tulajdonságát átbillenthetjük True-ra. Készen vagyunk az adatkapcsolattal, de mielőtt a megjelenítésre térünk át, tekintsük át az adatbáziskezelés kétségtelenül legfontosabb fogalmát, a tranzakciókezelést.
c) Tranzakciókezelés Tranzakción, logikailag összetartozó adatbázis műveleteket értünk, amelyet az adatok konzisztenciájának megőrzése miatt kell használnunk. Tehát az egy tranzakcióban lévő SQL utasítások mindegyikének sikerrel kell járnia ahhoz, hogy a tranzakció véglegesíthető legyen (Commit), ha valamelyik utasítás valami miatt mégsem hajtható végre vissza kell vonnunk (Rollback) a tranzakcióban levő valamennyi műveletet. Rendszerint minden formhoz rendelünk egy IBTransaction komponenst, ami a formhoz tartozó adatmodulban található. Minden adatelérési komponenst (queryket és dataseteket) ehhez a transaction komponenshez kell hozzákötni. Az adatmodulokon csoportosítjuk az adatbázis hozzáféréssel kapcsolatos komponenseket. Ha egy adattáblába a program írhat, editálhat, vagy törölhet belőle, akkor IBSajatDataSet-et használunk, adatok kigyűjtésére IBQueryt egyebekre az IB paletta egyéb komponenseit. Az IBSajatDataSet-re kiadott Posttal az adatok elpostázódnak az adatbázishoz, ilyenkor derül ki pl. ha van kitöltetlen mező, és sikeres post után a dataset dsBrowse módba kerül. A változtatások véglegesítése azonban kommittálnunk kell azokat. Ezt az adott datasethez kötött transakció kommittálásával tehetünk meg. A kommit viszont bezárja a tranzakciót, ezért használjuk a CommitRetaininget, ami a kommittálás után nyit egy másik tranzakciót, és mindent folytathatunk. Pl. IBTransaction.CommitRetaining; Ha Post után vissza akarjuk RollbackRetaininggel tehetjük meg.
vonni
a
változtatásokat,
akkor
ezt
a
61
Nagyon fontos, hogy ha csak a Post-ot adjuk ki, akkor az adatok csak addig látszanak, amíg a tranzakció él. Ha pl. bezárjuk a programunkat, és újra megnyitjuk meglepődve vehetjük észre, hogy egyetlen adat sem mentődött el, mivel a Post után nem hívtuk meg a Commit vagy CommitRetaining eljárások valamelyikét. Ügyeljünk tehát arra, hogy az alkalmazásunkban a felhasználónak ne legyen módja megtalálni az utat a Commit kikerülésére, mert ha csak apró lehetőséget is hagyunk neki erre, ő biztosan úgy fogja kezelni az ablakot, hogy magát büntetve, de minket bosszantva elveszejtse napi felvitt adatait. A Commit állandó használatának két ellenérve, hogy lassítja a működést, mivel a kommit sokkal hosszabb, mint a Post, illetve, hogy Commit után már nincsen lehetőségünk visszaállítani az előző állapotot, míg a Post után a RollBack elintézi ezt a szolgáltatást nekünk.
d) Adatmegjelenítés Mivel külön adatforrás komponens nincsen az IBX palettán, ezért a „Data Access” palettáról dobjunk az adatmodulra egy TDataSource komponenst, aminek a DataSet propertyjét állítsuk tblKeszlet-re, a neve pedig legyen dsKeszlet (16. ábra).
16. ábra Ezután a már megnyitott új Formra tegyünk egy DBGrid komponenst, az Align propertyje legyen alClient, és ha a uses listára beírtuk az adatmodulunk hivatkozási nevét akkor meg tudjuk adni a grid DataSource tulajdonságaként a „Fo_adatmodul.dsKeszlet” komponenst. Persze nem látszik semmi a megjelenő gridben, hiszen még nincsen adat benne, de ezt pótolhatjuk, és egyben tesztelhetjük a felrakott adatkomponenseinket.
62
Ahhoz, hogy ezt meg tudjuk tenni a keretformra vegyünk fel egy főmenüpontot „Törzsadatok” néven, majd hozzuk létre egy menüpontját, aminek legyen a címkéje: „Videókazetták”. Ha megnyitjuk a Delphi Project → Options menüpontját, akkor a Forms fülön látható, hogy az alakalmazás indításakor léterhozandó formok között (Auto-create forms) szerepel az datmodul is és a készletbeviteli form. Ez –amint arról már az előző félévben szó esett -, nem túl jó dolog, mivel ha sok ablaka van az alakalmazásunknak (márpedig a Kölcsönzőprogramunknak sok lesz), akkor nagyon sok hely lefoglalódik induláskor az alkalmazásunknak. Olyan formok is keletkezni fognak automatikusan, amiket a felhasználó meg sem fog nyitni munkája során. Ez nyilvánvalóan nem hatékony. Vegyük tehát ki a bal oldali ablakból az adatmodult és a formot, és hozzuk létre dinamikusan őket. Írjunk be az alkalmazásunk TMainMenu Törzsadatok → Videókazetták menüpontjának OnClick eseményébe valami ilyesmit: Application.CreateForm(TFo_adatmodul, Fo_adatmodul); Application.CreateForm(TfrmKeszlet, frmKeszlet); A fentebbi kód hatására, a menüpont kiválasztásakor létrehozódik először az adatmodul, azután pedig a form. Teszteljük az eredményt. Ha nem jelenítődne meg az ablak a kód hatására, akkor ellenőrizzük, hogy a megjelenítendő Készlet ablakunk FormStyle tulajdonsága fsMDIChild-e! A mentésnél jó ha Keszlet_temp nevet használunk, ugynis a végleges készlet ablakunk nem ez lesz, csak majd felhasználjuk hozzá. Mivel az alkalmazásunkban csak egyetlen adatmodult tervezünk (annak ellenére, hogy egy nagyobb rendszerben érdemes minden ablaknak saját adatmodult készíteni), tegyük vissza az adatmodult az induláskor automatikusan létrehozandó fájlok közé, és vegyük ki a menüpont OnClick eseményéből az adatmodul létrehozására beírt sort. Az előző fejezetben tanultaknak megfelelően állítsuk be a grid oszlopainak a felhasználó számára is értelmes neveket (17. ábra).
17. ábra Ha elindítjuk az alkalmazást, akkor felvihetünk adatokat a gridben (nem triviális az adatfelvitel menete, de kis próbálkozás után könnyen rá lehet jönni, hogyan is működik a grid). Az adatfelvitel kezelhetőségének a megnöveléséhez, tegyünk a formunkra egy DBNavigator komponenst, amit a Data Controls tab-on találunk. Állítsuk át a Datasource tulajdonságát dsKeszlet-re, és a TDBGrid Align propertyjét állítsuk át alClient-ről valami másra, és rendezzük úgy a form kinézetét, hogy esztétikusan el tudjuk rajta helyezni a dbnavigátor gombsortárat (pl. a navigátor Align legyen alBottom, a DBGridnek vegyük vissza újra alClientre az Align property 63
értékét) (18. ábra). Ne felejtsük a Hintet sem magyarítani! Ezután teszteljük újra az adatfelvitelt!
18. ábra Látható, hogy az adatok elmentődnek, függetlenül attól, hogy most le sem kezeltük az adatmentést. Ez két dolognak köszönhető: egyrész a DBGrid, nagyon intelligensen lekezeli az új rekord emelésekor az adatbázis Post-ot, másrészt az IBDataSet komponensünkre rákötött IBTransaction komponens sem marad tétlen, hanem elcommittálja automatikusan az adatbázisba a változásokat. Bár a biztonságos adatmentéshez ilyen esetben is célszerű kézzel lekezelni a Post-okat és a Commit-ot, de most egy olyan adatfelviteli módot nézünk meg, ahol a fennt említett automatizmusok már nem működnek rendesen. A felhasználók szeretik több szempontból megvizsgálni ugyanazt a dolgot, arról nem is beszélve, hogy valószínűleg különböző felkészültségű és múltú felhasználók másképpen fognak hozzáállni akár az adatfelvitel problémájához is. Hozzunk létre egy új gomot a Főablak eszköztárján. Legyen kétállapotú (Style = tbsCheck), Hint-jébe írjuk bele, hogy „Felviteli nézet váltása” és tegyünk rá egy olyan képet, amelyről nézetváltásra asszociálhat a felhasználó (keresgélni a Program Files\Common Files\Borland Shared\Images\Buttons könyvtárban érdemes). Az eszköztári gomb feladata az lesz, hogy ha megnyomja a felhasználó, akkor a grid nézetből egy kontroll-os adatfelviteli nézetre válthat, ahol minden mezőnek az értékét külön lehet megadni. Hogy ne zavarjon design közben a dbgrid bennünket, vegyük le az Align propertyjét alNone-ra, és húzzuk le valahová oldalra. Ezután tegyünk a formra minden mezőhöz egy-egy dbEdit komponenst (Data Controls paletta), és kössük rá őket a megfelelő mezőre és végezzük el a szokásos dolgokat (névadás, rendezés a formon). Tegyünk Label komponenseket a kontrollok fölé. (19. ábra) Vegyük észre,
64
hogy a megszűnt mező kezelésére célszerűbb egy DBCheckBox komponenst felraknunk.
19. ábra Ha megnyílik az ablak legyen alapértelmezett a grides nézet, és gomb lenyomására kezeljük a váltást. Nagyon fontos, hogy a DBGrid „elől” legyen, azaz jobb egérrel a komponensre kattintva a felugró menüből válasszuk a „Bring to Front” menüpontot. Ezután a FormCreate-ben kezeljük le az alapbeállításokat: procedure TfrmKeszlet.FormCreate(Sender: TObject); begin dbgAlap.Align:= alClient; dbgAlap.Visible:= True; //ezt nem is kellene, // de az "utánunk jövők" így //jobban értik majd mit is akarunk end; A következő feladatunk az lenne, hogy az eszköztári gomb megnyomásakor váltson nézetet az ablak. A probléma abban gyökerezik, hogy mi szeretnénk, ha ez a művelet nem csak erre az ablakra lenne érvényes, hanem bármelyikre az ezután elkövetkezendők közül is. Tehát általánosan kellene megfognunk ezeket az ablakokat, ami elég nehéz, főleg hogy azt sem tudjuk milyenek lesznek majd az ezután jövők. A MainForm.ActiveMDIChild meghívásával el tudjuk érni az aktív gyerekablakot, de ne mlátjuk annak egyetlen tulajdonságát se (ez természetes, hiszena Delphi sem tudhatja, hogy melyik lesz majd éppen az aktív ablakunk), rá tudnánk azonban kényszeríteni az frmKeszlet-et (MainForm.ActiveMDIChild as frmKeszlet) és ekkor már látszódnának az frmKszelet form komponensei is, de ez csak az frmKeszlet-re működik, a többinél szükségtelen hibákat eredményezne. És ugye mi egy szép, általános megoldást szeretnénk. A megoldást már ismerheted, egy közös ősformot kell létrehoznunk az ezután elkövetkezendő ablakaink számára. Az ősform létrehozásához egy új MDIChild
65
ablakra kell rátennünk egy DBGridet, amit elnevezünk dbgAlap-nak, a form neve legyen frmOsGyerek, és ne felejtsük a unitba beleírni a következőket: procedure TfrmOsGyerek.FormClose(Sender: Action: TCloseAction); begin Action := caFree; end;
TObject;
var
procedure TfrmOsGyerek.FormCreate(Sender: TObject); begin dbgAlap.Align:= alClient; dbgAlap.Visible:= True; end; Ezután mentsük el az új ŐsFormot olyan néven, ami hasonlít a form nevére (pl. OsGyerek). A legegyszerűbb módja a hibátlan örökítésnek, ha előlről kezdjük a készlet ablak létrehozását. Válasszuk a File → New menüpontot, majd a megjelenő Object Repositoryban kattintsunk a projectünk nevével fémjelzett fülre. Itt az frmOsGyerek ablakot használjuk ősként. Hogy ne vesszen kárba az eddigi munkánk, jelöljük ki az előző készlet ablakunkon az összes kontrollt (kivéve a dbgridet) és másuljuk a vágólapra, majd onnan az új örököltetett ablakunkra. Egy kicsit több kreativitás szükséges ahhoz, hogy a grid beállításait is átmásoljuk. Ehhez váltsuk át az eredeti készlet ablakunkat szöveges nézetre (a formon jobb egékattintás → View as Text) és keressük meg a dbgAlap komponensen belüla Columns kulcsszót: Columns = < item Expanded = False FieldName = 'NEV' Title.Caption = 'Film címe' Title.Font.Charset = DEFAULT_CHARSET Title.Font.Color = clWindowText Title.Font.Height = -11 Title.Font.Name = 'MS Sans Serif' Title.Font.Style = [fsBold] Visible = True end item… Másoljuk a vágólapra egészen a záró pipáig (>) az oszlop neveket és tulajdonságokat, majd az új készlet ablakunkban másoljuk be ezeket a dbgAlap deklaráció végére (előtte azt is át kell váltani szöveges nézetre). Ezután visszaválthatunk form nézetre (jobbkattintás a kódszerkesztőben és válassuk a „View as Form” menüpontot). Ezután már akár ki is vehetjük a projectből a Keszlet_temp unitot, vagyis az eredeti készlet ablakot (Project → Remove from Project…). Kössük át a keretablakon a menüpontban a Videókazetta törzsnél az új ablakra az eseményt, és ellenőrizzük a Project → Options ablakban, hogy nem automatikusan hozódik létre az új örököltetett ablak.
66
Ez az egész bűvészkedés arra volt jó, hogy most már van egy olyan ősünk, amin van egy dbgrid, és ehhez hozzá tudunk férni a keretrendszerből a következő módon: procedure TMainForm.tbNezetValtClick(Sender: TObject); begin if tbNezetValt.Down then (MainForm.ActiveMDIChild as TfrmOsGyerek).dbgAlap.Visible:= False else (MainForm.ActiveMDIChild as TfrmOsGyerek).dbgAlap.Visible:= True; end; Vagy ami sokkal egyszerűbb és elegánsabb, de ugyanazt csinálja: procedure TMainForm.tbNezetValtClick(Sender: TObject); begin (MainForm.ActiveMDIChild as TfrmOsGyerek).dbgAlap.Visible:= tbNezetValt.Down); end;
(not
Teszteljük az alkalmazást ezután, vegyünk fel néhány adatot mindkét nézetben (20. ábra).
20. ábra
67
Észreveheted, hogy az adatkontrollos nézetben nem igazán érződik, hogy mikor történik az adatmentés, és ha „ügyes vagy”, akkor még azt is elő tudod idézni, hogy az ablak újramegynitása alkalmavál elvesszenek az előzőleg felvett rekordjaid. Ez nem engedhető meg egy üzleti alkalmazásban, mint ahogyan az sem, hogy kétségek között hagyjuk a felhasználót, ezért nagyon fontos, hogy az ablak, illetve az alkalmazás bezárásakor kommittáljuk a változásokat, ha a felhasználó kéri. Ezt az alábbihoz hasonló módon vihetjük véghez: procedure TfrmKeszlet.FormClose(Sender: TObject; var Action: TCloseAction); var valasz : integer; begin inherited; Action := caNone; valasz := MessageDlg('Kívánja menteni a változtatásokat?',mtConfirmation, [mbYes, mbNo, mbCancel], 0); IF valasz = mrYes Then begin Fo_adatmodul.transKeszlet.CommitRetaining; end Else If valasz = mrCancel Then begin exit; end Else If valasz = mrNo Then begin Fo_adatmodul.transKeszlet.RollbackRetaining; end; Action := caFree; end; A következő fejezetben tovább finomítjuk adatbázisos ismeretinket, de ehhez nagyon fontos, hogy alaposan áttanulmányozd még egyszer ennek a fejezetnek az anyagát, és a legalább a kötelező feladatokat önállóan megoldd.
68
Kötelező feladatok 1. Készítsd el a VEVOK táblához a törzsformot, és tesztelésként vegyél fel néhány adatot a táblába! 2. Készíts a „műfaj” törzsadatokhoz, az adatbázisba egy táblát, generátort, és készítsd el hozzá Delphiben a formot! 3. A VEVOK táblához, adj hozzá egy BLOB típusú mezőt, ahol a vevők fényképeit fogjuk tárolni, és módosítsd az ablakot is ennek megfelelően! 4. Mit jelent a Read committed a tranzakcióban? Nézz utána az Interneten! 5. Ha kétszer kattintasz egy menüpontotra egymás után az ablak bezárása nélkül, akkor két példányban jelenik meg ugyanaz az ablak. Hogyan lehetne ezt elkerülni? Válaszd ki a legjobb megoldást, és módosítsd a kódot!
Szorgalmi feladatok 1. Készíts egy olyan táblát, és hozzá egy olyan ablakot, amelynek segítségével a felhasználó csoportosítani tudja a készleteit! Legyen a tábla neve KESZLETCSOPORTOK! Ez a gyakorlatban egy nagyon hasznos és fontos dolog, hiszen pl. majd szűrni tudja a kimutatásokat. Sokszor nem az egész készletünk mozgására, adataira vagyunk kíváncsiak, hanem csak egy részhalmazának az adataira. A készletcsoport és a készlet tábla között definiálj több-több kpacsolatot! Ez azt jelenti, hogy egy készletelem több készletcsoportba is tartozhat, és természetesen egy készletcsoport több készletelemet tartalmazhat. Implementáld ezt a Kölcsönző adatbázisban és az alkalmazásban is. Kapcsolótáblát kell használnod az adatbázisban és olyan komponenst a készlet ablakon, amellyel ez felhasználóbarát módon megvalósítható. 2. A kölcsönző alkalmazást minden ablakán lehessen szűrni az adatokat bizonyos szempontok szerint. Egy eszköztári gomb megnyomására jelenjen meg egy ablak, ahol a felhasználó beállíthatj a szűrés feltételeit (mező név helyett legyen a kontroll neve, amire az adott mező á van kötve). Jó lenne, ha több feltételt is meg lehetne adni valamilyen logika szerint, de az mindenképpen szükséges, hogy az ablak paraméterei dinamikusan töltődjenek be, így bármelyik gyerekablak esetén használható legyen a szűrés.
69
8. IBX II. rész a) Beléptető rendszer A LOGIN táblát még nem használtuk az adatbázisban. Ezt azért hoztuk létre, hogy mi tudjuk kontrollálni, hogy ki léphet be az adatbázisunkba és ki nem. Először hozzuk létre a beléptető ablakot (1. ábra):
1. ábra Fontos, hogy ne adatbázis kontrollokat használjunk. Editmezőnek a legjobb lenne, ha az előzőekben létrehozott saját komponens template-ünket, az AzEnterIsTab komponenst használnánk. Ez azért jó, mert amikor majd a felhasználók belépnek, ha a felhasználó név után enter-t ütnek, akkor is a jelszó mezőre lép a fókusz. Ehhez természetesen be kell állítanunk a tabordert, ezt tegyük is meg! Most azt kell megoldanunk, hogy először a beléptető ablak jelenjen meg, és addig legyen letiltva a főablak megjelenése. Ezt talán a legegeyszerűbben úgy oldhatjuk meg, hogyha a főablak OnCreate metódusában készítjük el Belépées ablakot, és megjelenítjük, de csak a sikeres belépés után hagyjuk végrehajtódni a főablak OnCreate-jét. Először vegyük ki az újonnan készített Belépés ablakunkat az AutoCreate formok listájából (ha ott van), ezután írjuk valami ehhez hasonló kódot: procedure TKolcsonzoKeret.FormCreate(Sender: TObject); begin Application.CreateForm(TfrmBelptet, frmBelptet); frmBelptet.ShowModal; if (not frmBelptet.OK) then Application.Terminate else begin frmBelptet.Free; Show; end; end; Hogy a fenti kód működjön, létre kell hoznunk a beléptető unitban egy OK publikus boolean változót. Próbáld ki mi történik, ha a beléptető ablak OnCreate metódusában
70
True vagy False értéket adsz neki! A fenti kód egyáltalán nem triviális. Kicsit elemezd, hogy mi miért van benne! Néhány szempont ehhez: • • •
A terminate elég brutális alkalmazás bezárás, miért lehet szükség erre? Miért kell a showmodal? Annélkül nem jelenne meg az ablak? Mire vonatkozik a show?
Most hagyjuk egy kicsit a beléptetést, és térjünk rá a felhasználó felvitelre. Készíts a 2. ábrához hasonló ablakot!
2. ábra A legjobb, ha itt sem adatbázis kontrollokat használsz. Egyetlen kivétel a listadoboz, amit ráköthetsz a LOGIN táblára (ehhez persze a fő adatmodulra fel kell venned a tblLogin komponenst IBDataSet formájában). Egy kis önálló programozási munkával tedd lehetővé, hogy az „Új felvitel” gomb megnyomására a kontrollok tartalma üres legyen, és a user be tudja írni az új felhasználói nevet és jelszót. Ha a „Rendben” gombra kattint, akkor történjen valami ehhez hasonló: If edJelszo.Text = edEllenorzes.Text then With Fo_adatmodul.tblLogin do Begin Append; //új sor emelése a LOGIN táblában FieldByName('FELHASZNALO_NEV').Value:= edFelhNev.text; // a FELHASZNALO_NEV mezőnek adjuk értékül a kontroll szövegét FieldByName('JELSZO').Value:= edJelszo.Text; FieldByName('BELEPETT').AsString:= 'F'; // a belépett mezőt az AsString minősítéssel szövegként értelmezzük Fo_adatmodul.tblLogin.Post; Fo_adatmodul.transLogin.CommitRetaining; End else ShowMessage(’Adja meg újra a jelszót és az ellenőrzést!’);
71
Ha a listadobozban valamelyik elemre rááll, akkor az arra vonatkozó adatok jelenjenek meg a bal oldali kontrollokban: Pl.: edFelhNev.text := Fo_adatmodul.tblLogin.FieldByName('FELHASZNALO_NEV').AsString; Ha a „Módosítás” gombra kattintanak, akkor szerkeszteni lehessen az adatokat a kontrolokban, egyébként nem árt az editmező ReadOnly tulajdonságát True-ra állítani. Szerkesztésnél a rendben gombnál megintcsak az új felvitelhez hasonló értékadás következik, az egyetlen különbség, hogy nem az „Append”-del kell új rekordot emelni, hanem az Edit eljárással kell a rekord szerkesztés módjába lépni. A „Törlés” gomb megnyomására jegyezd be a LOGIN tábla megfelelő rekordjába a TOROLVE mezőt ’T’-re. A „Mégse” és a „Kilépés” gombokat is kezeld le értelemszerűen. A „Mégse” a felvitel illetve a szerkesztés megszakítására vonatkozik, míg a „Kilépés” az ablak bezárására. Utóbbi esetben illik megkérdezni, hogy el akarja-e menteni a felhasználó a változásokat az adatbázisba. Az természetes, hogy az editmezők PasswordChar tulajdonságát „*”-ra vagy más tetszőleges karakterre állítjuk (a beléptető ablakon is!), de ezek mellett célszerű valamilyen titkosítási eljárást használni a jelszó adatbázisban tárolására. Ha valaki feltöri az adatbázis biztonsági rendszerét, akkor ne lehessen hozzáférni a felhasználók jelszavaihoz. Az alábbi kódrészlet erre szolgáltat egy megoldást egy publikus és egy privát kulcs használatával: procedure TfrmFelhasznKarbtart.Add; //kódolja adatokat és beírja azokat az adatbázisba const Key : array[0..7] of byte = ($11,$11,$11,$11,$11,$11,$11,$11); //privát kulcs
a
szükséges
var j, k, n : Integer; NewPwd, StorePwd : String; PwdIn, PwdOut : array[1..12] of byte; KeyData : TCryptData; begin NewPwd:= edJelszo.text; StorePwd:= ''; for j:= 1 to 12 do begin if NewPwd[j]= '' then begin for n:= j to 12 do PwdIn[n]:= 0; Break; end else PwdIn[j]:= Ord(NewPwd[j]); end; CryptInit(KeyData, @Key, SizeOf(Key)); 72
Crypt(KeyData, @PwdIn, @PwdOut, SizeOf(PwdIn)); //kódolás CryptReset(KeyData); CryptBurn(KeyData); for k:= 1 to 12 do case Length(IntToStr(PwdOut[k])) of 1 : StorePwd:= StorePwd + '00' + IntToStr(PwdOut[k]); 2 : StorePwd:= StorePwd + '0' + IntToStr(PwdOut[k]); else StorePwd:= StorePwd + IntToStr(PwdOut[k]); End; end; Az eljárás a következő unit függvényeit és eljárásait használja: unit JelszoTitkosit; interface uses Windows, Sysutils; type TCryptData= record Key: array[0..255] of byte; OrgKey: array[0..255] of byte; end;
// jelenlegi kulcs // erdeti kulcs
function CryptSelfTest: boolean; { Megvizsgálja, hogy működik e az adott helyzetben a titkosítás. Ha valami nem megy, akkor érdemes meghívni, hibátlan futás esetén True-t ad vissza. } procedure CryptInit(var Data: TCryptData; Key: pointer; Len: integer); { inicializálja a TCryptData rekordot, vagyis a két kulcsot } procedure CryptBurn(var Data: TCryptData); { "elégeti" a kulcsra vonatkozó összes infót } procedure Crypt(var Data: TCryptData; InData, OutData: pointer; Len: integer); { Mivel a kódolás szimmetrikus, ugyanezzel lehet kódolni és dekódolni. A Len a kódolandó plain text hossza byte-okban. } procedure CryptReset(var Data: TCryptData); { Kódolás ill. dekódolás után a láncolási információt semmisíti meg. Amennyiben csak egyszer adjuk meg a kulcsot, akkor egy-egy kódolás vagy dekódolás után mindig csak ezt kell megívni, ha minden kódolásnál és dekódolásnál megadjuk a kulcsot, akkor célszerű a CryptBurn-öt is használni, de előtte akkor is meg kell hívni a CryptReset-et.} 73
implementation {$R-} function CryptSelfTest; const InBlock: array[0..4] of byte= ($dc,$ee,$4c,$f9,$2c); OutBlock: array[0..4] of byte= ($f1,$38,$29,$c9,$de); Key: array[0..4] of byte= ($61,$8a,$63,$d2,$fb); var Block: array[0..4] of byte; Data: TCryptData; begin CryptInit(Data,@Key,5); Crypt(Data,@InBlock,@Block,5); Result:= CompareMem(@Block,@OutBlock,5); CryptReset(Data); Crypt(Data,@Block,@Block,5); Result:= Result and CompareMem(@Block,@InBlock,5); CryptBurn(Data); end; procedure CryptInit; var xKey: array[0..255] of byte; i, j: Integer; t: byte; begin if (Len<= 0) or (Len> 256) then raise Exception.Create('Crypt: Túl hosszú kulcs'); for i:= 0 to 255 do begin Data.Key[i]:= i; xKey[i]:= PByte(integer(Key)+(i mod Len))^; end; j:= 0; for i:= 0 to 255 do begin j:= (j+Data.Key[i]+xKey[i]) and $FF; t:= Data.Key[i]; Data.Key[i]:= Data.Key[j]; Data.Key[j]:= t; end; Move(Data.Key,Data.OrgKey,256); end; procedure CryptBurn; begin FillChar(Data,Sizeof(Data),$FF); end;
74
procedure Crypt; var t, i, j: byte; k: Integer; begin i:= 0; j:= 0; for k:= 0 to Len-1 do begin i:= (i+1) and $FF; j:= (j+Data.Key[i]) and $FF; t:= Data.Key[i]; Data.Key[i]:= Data.Key[j]; Data.Key[j]:= t; t:= (Data.Key[i]+Data.Key[j]) and $FF; PByteArray(OutData)[k]:= PByteArray(InData)[k] Data.Key[t]; end; end;
xor
procedure CryptReset; begin Move(Data.OrgKey,Data.Key,256); end; end. Értelmezd a kódot és illeszd be saját alkalmazásodba, hogy a jelszó titkosítva kerüljön az adatbázisba. Ezután visszatérhetsz a beléptető ablakhoz és megírhatod a beléptető eljárást, amit célszerű az „OK” gomb megnyomására rákötni. Ellenőrizd először a beírt felhasználói név helyességét, majd hogy a megfelelő jelszót írt-e be a belépni kívánó. Azt már tudod, hogy hogyan kell egy aktuális rekord egy mezőjének értékét kiolvasni vagy megváltoztatni. Itt arra is szükséged lesz, hogy rátalálj egy bizonyos rekordra. Erre két metódust használhatsz: a Locate ha megtalálta, akkor True-t ad vissza és rááll arra a rekordra, amit keresel, a Lookup csak kikeresi és visszaadja egy (vagy több) mező értékét, egy adott rekordban. function Locate(const KeyFields: String; Variant; Options: TLocateOptions): Boolean;
const
KeyValues:
function Lookup(const KeyFields: String; const Variant; const ResultFields: String): Variant;
KeyValues:
Pl. KodoltJelszo:= VarToStr(Fo_adatmodul.tblLogin.Lookup('FELHASZNALO_NEV', 'kutya', 'JELSZO'); Itt a „kutya” nevű felhasználó kódolt jelszavát kapjuk meg. A VarToStr függvényre azért van szükség, mert a Lookup Variant típust ad vissza, de feltettük, hogy mi itt 75
stringet várunk vissza (ha integer lenne, akkor erre nem lenne szükség, mivel a variant gond nélkül tekinthető integernek is). Ugyanez Locate-tel: Fo_adatmodul.tblLogin.Locate('FELHASZNALO_NEV', 'kutya', []); KodoltJelszo:= Fo_adatmodul.tblLogin.FieldByName('FELHASZNALO_NEV').AsString; A lookup nemcsak gyorsabb, de biztonságosabb is, általános szabály, hogy ha tudjuk kerüljük a Locare használatát! Természetesen itt is használnod kell a titkosító algoritmust. Ha a belépés sikeres, akkor egyszerűen az OK változót kell True-ra állítanod, ellenkező esetben adj False értéket neki. Fontos, hogy a felhasználó tudtára is adjuk, hogy hibázott, tehát a beléptető ablakra felrakott Label komponens szövegét változtasd át a megfelelő szövegre. Persze hagyjunk időt is, hogy elolvassa a felhasználó, mielőtt az alakalmazás bezárja magát, ehhez használjuk a Sleep(x) eljárást, ahol az x a várakozás ideje ezredmásodpercben. Ne felejtsük el belépés után bállítani a LOGIN tábla megfelelő rekordjában a BELEPETT mezőt ’T’-re. Az alkalmazásból kilépéskor pedig ’F’-re. Ügyelj arra, hogy az autocreate sorrend beállításával a Fo_adatmodul előbb jöjjön létre (3. ábra):
3. ábra Már egészen jó kis alkalmazásunk van beléptetőrendszerrel, és adatot is tudunk felvinni, viszont a lényegi funkcióra még nem használható, vagyis a kölcsönzött tételek nyilvántartására. Hozzunk létre táblákat az adatbázisban a következő szerkezettel: 76
CREATE TABLE MOZGASFEJ ( ID INTEGER NOT NULL, BIZONYLATSZAM VARCHAR(20) CHARACTER SET WIN1250 NOT NULL, ID_VEVO INTEGER, IRANY CHAR(1) CHARACTER SET WIN1250, ID_USER INTEGER, BIZONYLAT_DATUM DATE, BIZONYLAT_IDO TIME, CONSTRAINT MOZGASFEJPK1 PRIMARY KEY (ID) ); ALTER TABLE MOZGASFEJ ADD (ID_VEVO) REFERENCES VEVOK ALTER TABLE MOZGASFEJ ADD (ID_USER) REFERENCES LOGIN
CONSTRAINT MFEJCEGEKFKEY FOREIGN KEY (ID) ON UPDATE CASCADE; CONSTRAINT MFEJLOGINFKEY FOREIGN KEY (ID) ON UPDATE CASCADE;
CREATE TABLE MOZGASTETEL ( ID INTEGER NOT NULL, ID_MOZGASFEJ INTEGER NOT NULL, ID_KESZLET INTEGER NOT NULL, MENNYISEG NUMERIC(15,4) NOT NULL, NYILV_AR NUMERIC(15,4), ID_USER INTEGER, CONSTRAINT MOZGASTETELPK1 PRIMARY KEY (ID) ); ALTER TABLE MOZGASTETEL ADD CONSTRAINT MTETELFKEY1 FOREIGN KEY (ID_MOZGASFEJ) REFERENCES MOZGASFEJ (ID) ON UPDATE CASCADE; ALTER TABLE MOZGASTETEL ADD CONSTRAINT MTETELFKEY2 FOREIGN KEY (ID_KESZLET) REFERENCES KESZLET (ID) ON UPDATE CASCADE; ALTER TABLE MOZGASTETEL ADD CONSTRAINT MTETELFKEY3 FOREIGN KEY (ID_USER) REFERENCES LOGIN (ID) ON UPDATE CASCADE; create generator mozgasfej_id_gen; create generator mozgastetel_id_gen; Mielőtt a szkriptet lefuttatnánk, érdemes kilépni a Delphiből, vagy legalább bezárni a projektet, mivel ott megnyitottuk azokat a táblákat, amelyekre a kényszerek megadásánál most hivatkozni fogunk, és ezt nem szereti a Firebird. A szkript lefuttatása után hozzuk létre az adatmodulunkban a táblákhoz a megfelelő komponenseket (4. ábra). Mindent az előzőekben megszokott módon kell tennünk, kivéve a tblMozgastetel tábla esetén, ahol a DataSource property-t állítsuk dsMozgasFej-re, és a SelectSQL tulajdonság pedig legyen a következő: select * from mozgastetel where id_mozgasfej = :id (5. ábra).
77
4. ábra
5. ábra
78
Ez a hozzárendelés biztosítja, az ún. master-detail kapcsolatot, vagyis a tétel táblában csak mindig azok a rekordok látszanak, amelyek a fej táblában aktív rekordhoz tartoznak. Ha örököltetsz egy új formot a kölcsönzésnek és feldobsz még egy dbgridet, és rákötöd az örköltetett gridet és az újat is a megfelelő datasetekre, akkor láthatod, hogy itt már vannak olyan mezők is, amelyek idegen kulcsok, tehát egy másik táblában található rekordra hivatkoznak: pl. id_vevo (6. ábra).
6. ábra Ezek helyén lookup mező értékét kell természetesen megjeleníteni. Ehhez dupla kattintással jelenítsük meg a tblMozgasfej komponens Fields Editor-át (7. ábra). Adjuk hozzá az összes mezőt (jobb egér kattintás, Add All Fields).
7. ábra Ezután szintén jobb egérkattintás és válasszuk a New field… menüpontot (8. ábra).
79
8. ábra És a 9. ábrának megfelelően töltsük ki a lookup mező adatait.
9. ábra Hasonlóan járjunk el az id_keszlet mezőnél is, majd a gridbe az így nyert lookup mezőket kössük be. Tulajdonképpen készen is lennénk, de azért van még egy-két probléma. A legfontosabb, hogy a tétel felvitelénél, a tétel táblába nem jegyződik be automatikusan az id_mozgasfej értéke. Ezt nekünk kell megtenni kódból, de ügyeljünk arra, hogy csak akkor jegyezzük be ezt, ha valóban új rekordot vettünk fel. Ezt leginkább az alsó dbgrid OnEnter eseményében tehetjük meg, ha a tblMozgasfej.State property-jét vizsgáljuk. Az ID_USER mezőbe a háttérben menstük el, hogy melyik felhasználó vette fel, vagy módosította legutóljára az adatokat. Természetesen az ID és ID_MOZGASFEJ mezőknek sem kell látszódni az ablakon. Hozzuk létre a fej tábla kontrollos nézetét is, mint azt tettük a törzsablakokon. A mozgastételekhez természetesen maradhat a dbgrid, hiszen azt nehezen tudnánk kontrolokkal tesztelni. Teszteld alaposan az ablakot!
80
Kötelező feladatok 1. Valósítsd meg a törlést a kölcsönzés ablakon! Ügyelj arra, hogy a tétel rekordok is törlődjeneke, ha egy fejrekordot törlünk, de külön is lehessen törölni egy tétel rekordot! 2. Jelenítsd meg az aktuális rekord legutolsó módosítójának felhasználói nevét a keretform státuszsorában! 3. Kezeld le a mozgás irányát, vagyis hogy visszahozza vagy elviszi valaki a videókazettát! Kontrollos nézetben ehhez használj dbchekboxot.
Szorgalmi feladatok 1. Jelenítsd meg felvitelkor a vevő fényképét az ablakon! 2. A Quickreport fül segítségével készíts nyomtatható bizonylatot az egyes kölcsönzések adatairól. 3. Gyűjtsd össze mi hiányzik még az alkalmazásból, hogy egy valódi videókölcsönzőnél is használható legyen! Valósítsd meg ezeket a funkciókat!
81
XML (dokumentumok között leírás), DLL(Cantu), thread és process kezelés(Delphi kézikönyv)
82