Delphi - Hálózati adatbázis-kezelő alkalmazás MIDAS nélkül

2. rész

forráskód letöltése
Múlt héten megkezdett alkalmazás fejlesztésünket folytatjuk tovább. A mostani cikkben az alábbiakat tárgyaljuk a paradox-os hálózati adatbázis kezelő rendszer fejlesztésénél:
- A programfelület megtervezése és applikálása az adatbázisra
- A hiba és kivételkezelés pontjainak feltárása és programozása
- A hálózati programelérés megvalósítása Megjegyzés: A program áttekintése előtt mindenképp olvassuk el a cikket. Ha még nem lenne meg, akkor készítsük el az szgep aliast a BDE Administrator segítségével! (pl. c:\work lehetne az útvonala). A programhoz szükséges adatbázisok a múlt heti cikkben lettek létrehozva és annak mellékletében találhatók.


A programfelület megtervezése és applikálása az adatbázisra

Az 1. cikk kapcsán közétett elindítását követően tanulmányozhattuk a program adatbázisszintű összefüggésrendszerét. Erre az összefüggésrendszerre kell létrehozni a user interfészként létrehozandó beviteli Form-okat.
Alapvetően 5 Form-ot kell létrehozni:

1. Fő Form: innen fogjuk elérni az egyes al Form-okat.
2. Form: a hardveres adatok rögzítési Form-ja és lekérdezési Form-ja
3. Form: szoftveres adatok rögzítési és lekérdezési Form-ja
4. Form: mozgásadatok rögzítési Form-ja
5. Form: a 3. cikkben ismertetésre kerülő jogosultsági rendszerkezelő felület Form-ja

A programfunkciók megtervezése és így a Form-ok létrehozása alapvetően az adatbázis-tervezés logikai menetét követi. A Form-ok létrehozásához a Form wizard-ot fogjuk használni, melyet a database menüponton belül találjuk.
A fő Form-on el kell helyezni 7 db nyomógombot.
Ezek a következők:
1. Számítógépes hardveradatok felvétele, módosítása
2. Számítógépes szoftveradatok felvétele, módosítása
3. Számítógépek mozgásadatainak rögzítése
4. Jogosultságkezelő
5. Kilépés
6. Login
7. Új password

Ezek közül rendre csak az 1, 2, 3, 5 nyomógombokra generált Form-ok és parancsok elkészítésével foglalkozunk ebben a cikkünkben.

1. Számítógépes hardveradatok felvétele, módosítása Form:

Létrehozásához kattintsunk a Form wizard menüpontra. Egyszerű, egyetlen táblát kezelő Form-ot fogunk létrehozni. Ki kell jelölnünk az szgep alias szgep.db tábláját, illetve annak összes mezőjét meg fogjuk jeleníteni a készülő Form-unkon. DBGrid komponensként jelenítjük meg az egyes hardverek rekordjait. Végeredményként automatikusan legenerálódik a Form az adatbázis navigátor komponensével együtt. Ne feledjük az adatbázis könnyebb kezelhetősége végett a Datamodule-t legeneráltatni. Az előbbiekben leírt követelmények opcionálisan be vannak építve a Form varázsló létrehozási lépéseibe. Csak be kell kattintani a rádiógombokat, így aktiválhatjuk őket.
A Datamodule egy külön ablakban fogja össze a táblák és adatforrások komponenseit, így egy-egy Form adatbázis-hivatkozásai könnyedén felügyelet alatt tarthatók segítségével. Természetesen Datamodule-ok Form-onként külön-külön létrehozhatók és sorszámot kapnak.
Amikor legeneráltuk a Form-ot, néhány szoftverből vezérelt funkciót kell írnunk a hardveradatok rögzítését szolgáló DBGrid komponens eseményeihez.

Ezek a következők:

Az SZ_ID elnevezésű szgep.db tábla mezőjéről már tudjuk, hogy a táblák közötti szülő-gyermek kapcsolatért felelős. Tartalma kötelező és egyedi. Nem létezhet két azonos azonosítóval ellátott rekord. Ezt a primary key tulajdonsága biztosítja. Tartalommal való feltöltését a DBGrid komponens SZ_ID mezőjébe való kattintással oldjuk meg (DBGrid1ColEnter), amely esemény bekövetkeztekor a következő programkód fut le:
procedure TForm3.DBGrid1ColEnter(Sender: TObject);
begin
  Datamodule2.table1.edit;
  try
      try
       if (Form3.dbgrid1.fields[0].value  null and Form3.dbgrid1
        .fields[1].value  null) then
       begin
        Form3.dbgrid1.fields[2].value:=inttostr(Form3.dbgrid1
           .fields[0].value)+inttostr(Form3.dbgrid1.fields[1].value);
        Datamodule2.table1.fieldbyname('SZ_ID1').asstring :=
           Form3.dbgrid1.fields[2].value
       end;
        except on EDatabaseError do begin
	Showmessage('Hiányzó kötelező adatok!');
        end;
      end;
    except on EVariantError do begin
    end;
  end;
end;

Konkatenációt végzünk az előzetesen kitöltött SZOBASZAM és GEPSZAM tartalmára és az így generált sztring kerül az SZ_ID mezőbe. Mindhárom említett mező kötelező érték. SZ_ID azonosító nélkül nem menthetünk el rekordot. Jól látható a NULL érték vizsgálata a két mezőre (Form3.dbgrid1.fields[0].value <> null and Form3.dbgrid1.fields[1].value), ami az egyszeri feltölthetőséget biztosítja, elkerülve a véletlen felülírás lehetőségét. Ami a konkatenációt illeti egyszerű + művelettel elvégezhető. Az SZ_ID1 mezőt bár technikailag nem használjuk a programban, itt egy példa az adatbázisszintű DBGrid érték feltöltését kikerülő módszerre: (Közvetlen adatbázis hozzáférés.)
Datamodule2.table1.fieldbyname('SZ_ID1').asstring :=Form3.dbgrid1.fields[2].value. A példa jól szemlélteti az adatbáziscella közvetlen hozzáférési lehetőségét.

A user nem írhatja felül az SZ_ID tartalmát, mert annak az lenne a következménye, hogy nem töltené be funkcióját az azonosító és ez hibákhoz vezetne. Ezért a valós életben használt szoftvereknél az ilyen jellegű mezők rejtve maradnak a felhasználó elől. (Akár az SZ_ID1 mező esetében. Nézzük meg Database Desktoppal hogy van-e tartalma!). A mi programunk azonban demonstrációs jelleggel készült, hogy jól szemlélhessük az azonosítás logikáját. Azért hogy ne lehessen felülírni az SZ_ID, SZOBASZAM, GEPSZAM mezők tartalmát, a következő kódot építettük a programba: A kód végén található EvariantError magyarázata A hiba és kivételkezelés pontjainak feltárása és programozása című részben lesz leírva.
procedure TForm3.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
  Datamodule2.table1.edit;
  try
    Form3saved:=false;
    if Form3.dbgrid1.selectedfield.fieldname = 'SZ_ID' then
     begin
     Form3.dbgrid1.enabled := false;
     showmessage('Nem szerkeszthető mező: SZ_ID!');
     Form3.activecontrol:=dbnavigator;
     Form3.dbgrid1.enabled := true;
   end;
  if Form3.dbgrid1.selectedfield.fieldname = 'SZOBASZAM' then
    if Form3.dbgrid1.fields[0].value  null then
     begin
       Form3.dbgrid1.enabled := false;
       showmessage('Már nem szerkeszthető cella: SZOBASZAM!');
       Form3.activecontrol:=dbnavigator;
       Form3.dbgrid1.enabled := true;
     end;
  if Form3.dbgrid1.selectedfield.fieldname = 'GEPSZAM' then
     if Form3.dbgrid1.fields[1].value  null then
     begin
     Form3.dbgrid1.enabled := false;
     showmessage('Már nem szerkeszthető cella: GEPSZAM!');
     Form3.activecontrol:=dbnavigator;
     Form3.dbgrid1.enabled := true;
   end;
  except on EVariantError do begin
  end;
 end;
end;

Vagyis röviden összefoglalva az SZ_ID mezőbe soha, a másik két mezőbe csak üres állapotban írhatunk értéket, amit azok NULL vizsgálata fejez ki. Ha azonban Delete gombbal először kiürítjük a cella tartalmát, akkor újra elkezdhetjük a feltöltésüket. Nos, ez a megoldás csak részben hárítja el a véletlen felülírás problematikáját, bár ha az adott konfigurációnak változik a cégen belüli szobaszáma vagy gépszáma, akkor bizony használható a Delete gomb. Ily módon generálhatunk új azonosítót az áthelyezett gép számára. Ez természetesen kihat a gyermek rekordok azonosítóira is, amelyek automatikusan átíródnak a szülő rekord új azonosítójára. Ezt az indexelés maintained jellege biztosítja. (Nem vész el a gyermek.)
A DBGrid komponens fontos tulajdonsága, hogy mezői az adatbázis rekordjait mutatják, illetve a rajta végzett dupla kattintás tulajdonságainak szerkesztő ablakát jeleníti meg. Itt limitálhatjuk a megjelenítendő mezőket, vagy adhatunk hozzá újat. De az adattáblából kapott mezőneveket - melyek a DBGriden megjelennek - itt át is írhatjuk, hogy "magyarosabb" legyen a programunk. (Ehhez a property window-ot kell használni a DBGrid Editor megjelenítését követően.)
A program használata során, ha egy DBGrid rekordról áttérünk egy másik rekord szerkesztésére, akkor az adatbázisba bekerül az előző rekord DBGrid-es tartalma, feltéve, ha nem generálódott adatbázis kivét.
Ha egy Form-ot legeneráltunk, de az adatbázis azt követően még változik, mezőit tekintve bővül vagy csökken, szükség lesz arra, hogy ezt a programmal is közöljük. Először is a Datamodule megfelelő TTable komponenséhez hozzá kell adni vagy elvenni a megfelelő mezőnevet (Fields editor), majd ugyanezt el kell végezni a DBGrid komponenssel is (DBGrid Editor). Ezt a folyamatot azért érdemes tudni, mivel egy Form-on még számos programozási feladatot végrehajtunk és időközben számos adatbázisszintű változtatás felvetődhet, tehát ne csak a Form generálás legyen az egyetlen eszközünk az adatbázis Form összefüggésrendszerének karbantartására, hanem egy manuális korrekció lehetősége is rendelkezésünkre álljon. Gondoljuk csak el, hogy a Form-hoz írt időigényes kódjainkat állandóan újra kellene írjuk.

Ne feledjük a fő Form megfelelő nyomógombjához hozzárendelni a létrehozott Form-ot. Ez a következő egyszerű paranccsal történik:

Form3.visible:=true;
Vagyis láthatóvá tettük az eddig láthatatlan Form-ot. Fontos, hogy minden al Form legyen láthatatlan a program indításakor.

Szoftveradatok rögzítése Form:

A szoftveradatok rögzítését lehetővé tévő Form előállítása hasonlóan történik az előzőekhez, azzal a különbséggel, hogy nem egy tábla, hanem két tábla fog szerepet játszani működése során. Ez alapvetően a szülő-gyermek kapcsolat Form szintű megjelenítését feltételezi. A dolgunk csak annyi, hogy a master-detail Form opciót aktiválnunk kell a Form varázslóban. A felhasználandó táblák az szgep.db és a szoft.db lesz. A kapcsolatot a primary key-ként, s egyben strict referential integriry-ként deklarált SZ_ID mezőn keresztül valósítjuk meg. Erre már az 1. cikkben is utaltunk. Ezért ezt a mezőt mindkét táblából ki kell választani a Form varázslóban. Így valósul meg a szülő-gyermek kapcsolat.
A hardveradatokat DBEdit komponensként, míg a szoftveradatokat DBGrid komponensként jelenítjük meg a Form-on. Mivel itt nem szeretnénk a hardvertábla adatait szerkeszteni, ezért az összes DBEdit komponens Readonly tulajdonságát állítsuk True-ra.
A DBGrid szoft.db.SZ_ID mezőjébe a kapcsolatot biztosító szgep.db. SZ_ID mező tartalma be kell hogy kerüljön, ezért ezt a következő kóddal biztosítjuk:
procedure TForm5.DBGrid1ColEnter(Sender: TObject);
begin
  datamodule4.table2.edit;
  Form5.dbgrid1.fields[0].value:=Form5.editSZ_ID.text;
end;
Ezzel át is másoltuk a megfelelő értéket az editSZ_ID.text-ből a dbgrid1.fields[0].value-ba.
Természetesen itt is meg kell akadályozni az SZ_ID tartalmának felülírhatóságát:
procedure TForm5.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
  Form5saved:=false;
  if Form5.dbgrid1.selectedfield.fieldname = 'SZ_ID' then
  begin
   Form5.dbgrid1.enabled:=false;
   Showmessage('Nem szerkeszthető mező: SZ_ID!');
   Form5.activecontrol:=dbnavigator;
   Form5.dbgrid1.enabled:=true;
  end;
end;
Vagyis ha az adott mező DBGrid-jének celláján billentyűzet leütés történik, akkor írhatatlan lesz a DBGrid komponens és egy hibaüzenet is érkezik. Az aktívkontroll (cursor) pedig "kirepül" a DBGrid-ből a dbnavigátorra.

Ne lepődjünk meg, hogy itt nem tudunk több DBGrid rekordot generálni, mivel a szülő-gyermek kapcsolat 1-1. Ez azért van így, mert egy számítógéphez általában 1 féle szoftverkonfiguráció tartozik.
Ne feledjük a megfelelő nyomógombhoz hozzákapcsolni a létrehozott Form-unkat a Form5.visible:=true; paranccsal.

Mozgásadatok rögzítése

A harmadik Form létrehozása megegyezik az előző pontban leírtakkal. Azonban a kapcsolat nem 1-1, hanem 1-többes. Ezt úgy értük el, hogy a sequence mezőt definiáltuk primary indexként. Így már lehetővé vált a secondary indexek, illetve referential integrity definiálása is, ami gyakorlatilag az SZ_ID lesz. Ez azonban mivel most nem primary index, lehetővé teszi a strict megszorítás figyelmen kívül hagyását. Ezért lehet több rekordot rögzíteni egy szülőhöz. Itt is megoldandó a kapcsolatot biztosító SZ_ID feltöltése. Ez automatikusan megtörténik, ahogyan a programban is megfigyelhető. Datamodule6.table.fieldbyname('SEQUENCE'):=Datamodule6.table.fieldbyname('SEQUENCE'); sor biztosítja a DBGridKeyPress-ed eseménynél, hogy a sequence mező töltve legyen. Hiába volt autoincrement típus, követeli az értékét, bár oda bármit írhatunk csak a saját számsorrendjét fogja követni a feltöltésben. Ha ezzel az ügyes trükkel "hozzápiszkálunk" máris feltölti önmagát a soron következő elemmel. Ha eltüntetjük az SZ_ID illetve SEQUENCE mezők láthatóságát a DBGridben, akkor egy olyan programhoz jutunk, amely olyan technikát használ, amellyel a valóságban működő programok is dolgoznak. Tehát, nem szabad mindent láttatni a userrel.

Megjegyzendő az ALLAPOT illetve MOZGAS JELLEGE mezők kötelezően válaszható értékeinek definiáltsága, melyet az adatbázis-tervezés során már megtettünk. ({+,-} {F/Z}).
Ezt a DBGrid Editor segítségével az úgynevezett Picklist editorban tudjuk programszinten megjeleníteni, vagyis egy legördülő lista generáltatható a DBGrid megfelelő mezőihez, amelyekben megjelennek a kitöltendő értékek lehetőségei. Ahhoz, hogy ez a legördülő ablak megjelenjen, szükséges az adott mező esetében a DBGrid editor ButtonStyle tulajdonságát cbsAuto-ra állítani.

A továbbiakban a hardver és szoftver rögzítését szolgáló Form-okat érintő azonos funkciókról lesz szó:

A hardver illetve szoftver Form-hoz létrehoztunk egy mentés gombot. A gomb mögött a következő utasítássor áll:
procedure TForm3.Button4Click(Sender: TObject);
begin
  try
    Datamodule2.table1.edit;
    Datamodule2.table1.post;
    Form3saved:=true;
  except on EdatabaseError do
    begin
      Showmessage('Nincs minden kötelező adat kitöltve!');
      Form3saved:=false;
    end;
  end;
end;
Vagyis editáló üzemmódba kapcsolunk és mentünk. A Formxsaved változót használjuk a mentettség vizsgálatára. Ha ennek az értéke igaz, akkor sikerült a mentés, ha nem, akkor valami gond lépett fel mentés során. Például egy kivét generálódott. EdatabaseError esetén nem adtunk meg minden mezőértéket. Ezt közölhetjük is a userrel, ahogyan a fenti példában tettük.
Ez a két Form bezárás előtt kiszól, hogy mentsünk: Ennek vizsgálatát a FormClose eseményhez rendeltük. Látható, hogy a Formxsaved változó állapota jelzi vissza a mentettség állapotát, vagyis erre vizsgálódunk. A DBGridKeyPress esemény bekövetkeztekor mindig False-ra állítjuk minden egyes Formxsaved változó értékét, így ez biztosítja az állandó mentési figyelmeztetést. A Formxsaved-ban az x érték = Form sorszáma. Tehát annyit definiálunk belőle, ahány mentendő Formunk van.
procedure TForm3.FormClose(Sender: TObject; 
   var Action: TCloseAction);
begin
  Datamodule2.table1.edit;
  if Form3saved=false then
    if MessageDlg('Menti a változásokat?', mtInFormation, 
        [mbYes, mbNo], 0) = mrYes then
    begin
      Datamodule2.table1.post;
    end;
end;
Minden szóba került Form esetében szükséges keresőfeltételek beiktatása a munka megkönnyítése végett. Ezt a secondary indexek segítik. A DBLookUpComboBox komponensben jelennek meg a keresési feltételek elemei azok növekvő sorrendjében. Itt buborék keresési eljárással könnyedén megtalálhatjuk a keresett rekordot. (Sorba beírjuk a keresett elem karaktereit.) A keresési szempont megadása előzetesen a megfelelő keresési szempont gombra történő kattintással történik. Ekkor a DBLookUpComboBox indexe kicserélődik a keresési szempont szerintire, így kerülnek abban a megfelelő elemek.

Egy példa az SZ_ID szerinti keresés esetében:
procedure TForm3.Button3Click(Sender: TObject);
begin
  Datamodule2.table1.indexfieldnames:='SZ_ID';
  DBlookupcombobox1.listfield:='SZ_ID';
  DBlookupcombobox1.keyfield:='SZ_ID';
  Label3.caption:=button3.caption;
end;
A program amellett, hogy az aktuális indexfájlt beállítja, elvégzi a DBlookupcombobox1.listfield-jének újradefiniálását, valamint keyfield-jének értékadását, sőt a képernyőre kiírattuk az aktuális keresési feltétel szerinti indexelést is:

A Label3.caption:=button3.caption; sorral.

A fő Form kilépés gombja egy egyszerű halt parancsot ad ki.

A hiba és kivételkezelés pontjainak feltárása és programozása.

Alapvetően a mezőkitöltöttséggel kapcsolatban jelenik meg a kivételkezelés igénye, amikor a mentéseket végezzük. Ekkor az EdatabaseError kivétet kell lekezelni és megfelelő szöveges outputot kell megjeleníteni. (Lsd. pl.: procedure TForm3.Button4Click(Sender: TObject);)
Az adattípus helyessége szintén automatikus vizsgálat tárgya. Az EvariantError konvertálásból fakadó idegesítő hibaként jelent meg a NULL érték vizsgálata során. Lekezelésére egyszerűen azt a trükköt alkalmaztuk, hogy "átfolyik" rajta a program, vagyis nem történik semmi. (Pl.: procedure TForm3.DBGrid1KeyPress(Sender: TObject; var Key: Char);)

A hálózati programelérés megvalósítása

A hálózati programelérés alapjait már leírtuk az 1. cikkben. Hivatkoztunk az 'szgep' alias létrehozására és útvonal hivatkozására, mely a programot és adatbázisát tartalmazó felmappelt hálózati meghajtó betűjelét és könyvtárát jelenti. Mindezt a bdecfg32.exe (BDE Administrator) programmal létrehozhatjuk. Továbbá létre kell hozni egy parancsikont a hálózati útvonalon található programra, amely elindítja azt. Ezt az asztalon is létrehozhatjuk jobb klikk, új parancsikon végrehajtásával. Csak a megfelelő útvonalat és programnevet kell beírnunk. Azonban, hogy ne azonos jogokkal férjenek a funkciókhoz, szükséges egy jogosultsági rendszert írni a programhoz, melynek részleteivel a következő cikkben ismerkedünk meg.
A második cikkhez mellékeltünk programkódot. A kód Delphi 4.0-ban lett írva. Ne feledjük az 1. cikkben mellékelt adatbázisokat is felmásolni a gépre. Ezt követően az szgep aliast kell ellenőrizni, hogy létre van-e hozva és jó útvonalra mutat-e. (BDE Administrator-ral.)
Konyeczki László
agrárinformatikai mérnök
Debrecen
konyeczkil@hotmail.com

Hálózati adatbázis-kezelő alkalmazás MIDAS nélkül cikksorozat