Ezen cikkünkben példa-programunkhoz, a Telefonkönyv-höz kereső rutint írunk. Egyik előző cikkünkben már elkészítettük a szükséges ablakok egy részét, így most csak kereső rutin megvalósítására szorítkozunk bővebben. Tökéletes és gyors kereső rutin írása felettébb bonyolult dolog lehet első nekifutásra. Egy igazi kereső rutinnál természetesen a keresett szöveg tartalmazhat jocker karaktereket, vagy egyéb több funkciós operátorokat. A jocker karakterek esetén maradhatunk akár a jó öreg DOS-os karaktereknél, ahol a ? karakter jelenti egy karakter pótlását, illetve a * karakter bármely karaktersorozat pótlását jelentheti. Számos operátort is használhatna a kereső rutin, hogy megadhassuk, hogy pl. a keresett sztring 2. karaktere csak a vagy b betű lehet (['a', 'b']). Mi az egyszerűség kedvéért, illetve az alapok szükséges ismertetése végett, a dolgok egyszerűbbik végét választjuk, egy sima kereső algoritmust, mely csak adott karakterláncot keres pár lehetőséggel bővítve. (Valljuk be egy telefonkönyvnél nincs is szükség ilyen részletes - akár karakterenkénti tartomány megadására is képes kereső algoritmusra. )

A kereső dialógus

Először ismertetjük a kereső dialógusát, az elkészítendő form-okat és egyéb használt vizuális komponenseket. Két form-ra lesz szükség egy, ahol megadjuk a keresendő szövegmintát a másik, ahol a találatokat jelezzük ki, pl. egy listbox-ban. Lássuk a kereső dialógust:

A keresés dialógusunk - ahogy azt más programoknál is megszokhattuk - több részből áll:

  • Egy általunk gyors (egyszerű) keresésnek megnevezett keresési módszer mely a keresendő szöveget az általunk kijelölt mezőkben keresi (Miben keresse). A keresés alap esetben nem érzékeny a kis- és nagybetűk különbségére, bekapcsolható, hogy az ékezeteket se vegye figyelembe. Továbbá a keresés alapesetben megjeleníti az összes olyan találatot, ami a fenti keresendő szöveggel kezdődik, tehát, ha "Próba" a keresendő szöveg, akkor "Próba*"-ot avagy "Próbavalamit" is találatnak értékeli. Ezt a lehetőséget a "Csak ha a megadott szöveg a teljes szó" kapcsolóval ki lehet kapcsolni, így a keresett szöveg teljes egyezősége esetén kapunk csak találatot. Az "Azonos személy találatainak kiszűrése" kapcsolóval pedig az egy személy adatlapjához tartozó több - de különböző keresési mezőben történt - találatot szűrhetjük ki így, ha több találat is van, akkor is csak egyszer jelezzük. A keresés más programoknál megszokott módon történhet előröl, illetve hátulról, vagy a telefonkönyv aktuális pozíciójától.
  • A részletes keresésnél már minden keresési mezőt pontosan definiálhatunk, így elkerülhetjük a fölösleges adatlapok kilistázását. Az érdekesség az az, hogy az előző lapnál beállított lehetőségek itt is érvényesek, az-az: A kis- és nagybetűk megkülönböztetése, az ékezetes karakterek helyettesítése, illetve a teljes szóra történő keresés. Továbbá itt is érvényes az előző oldalon megadott keresési mód: előröl / hátulról, illetve aktuális pozíciótól. Az üresen hagyott mezők nem kerülnek összehasonlításra!
A kereső, pontosabban a keresést beállító dialógusokról ennyit dióhéjban, most a keresés eredményét megjelenítő dialógus megtervezésével zárjuk a vizuális komponensek használatát:

A keresés eredményét egy listában (de TStringGrid-del) jelenítjük (pár járulékos információval). A megtalált adatlapok között közvetlenül, vagy a gombokat (Előző/Következő) használva navigálhatunk. Az "Adatlap mutatása" lehetőséget bekapcsolva a kiválasztott adatlap rögtön megjelenik.

A kereső dialógussal összefüggő teendők

Érdemes pár, a felhasználó dolgát megkönnyítő apróságot programunkba integrálni.

  • Ha nincs szöveg a keresési mezőbe gépelve, akkor ne lehessen a kigyűjtés gombot lenyomni, az-az letiltjuk. Ezt mind az egyszerű és, mind a részletes keresésnél megtesszük:

  •  

     

    procedure Tfinddialog.szovegChange(Sender: TObject); begin // a kigyűjtés gomb tiltása, vagy engedélyezése a beírtaktól függően   kigyujt1btn.Enabled := Length(szoveg.text) <> 0; end;
    procedure Tfinddialog.vnevChange(Sender: TObject); begin // a kigyűjtés gomb tiltása, vagy engedélyezése a       //beírtaktól függően   kigyujt2btn.Enabled := Length(vnev.text)+Length(knev.text)+    Length(enev.text)+Length(varos.text)+Length(irsz.text)+    Length(utca.text)+Length(hazsz.text)+Length(tel.text)+    Length(email.text)+Length(web.text) > 0;end;
     

    Ha az adott keresési fülre klikkelünk, akkor rögtön a keresési mezőre ugorjon a kurzor (avagy a fókuszt oda adjuk át):
     
     

    procedure Tfinddialog.PageControl1Change(Sender: TObject);begin   if PageControl1.ActivePage.PageIndex = 0   then szoveg.SetFocus  //egyszerű keresés   else if PageControl1.ActivePage.PageIndex = 1        then vnev.SetFocus;  //részletes keresés end;

A kereső algoritmus, a keresés folyamata

A gyors keresés

A gyors (egyszerű) keresésnél csak akkor indítjuk el a keresést, ha új szöveget keresnek - ez csupán takarékossági ok. Majd a beállításoktól függően vagy ékezettelenít (pl. é => e; á => a) vagy, ha nem számít a kis- és nagybetű közötti különbség, akkor kisbetűssé alakítja mind a keresett és mind az összehasonlítandó szöveget (ez utóbbira később kerül sor).
 
 

procedure Tfinddialog.kigyujt1btnClick(Sender: TObject);var   UjKereses: Boolean;   i, SrcNo : Integer;begin   KerSzoveg:=szoveg.Text;  // a keresett string átadása a helyi rutinnak   if ekezetekcbox.Checked then      KerSzoveg:=UnAccenter(KerSzoveg); //ékezettelenítés   if Not kisbnagybcbox.Checked then      KerSzoveg:=LowerCase(KerSzoveg); //kisbetűsítés   UjKereses:=TempSzoveg <> KerSzoveg;   if UjKereses then //új keresésről van szó  begin     TempSzoveg:=KerSzoveg; SrcNo:=1;     telkonyv_kereses_eredmeny.kerlista.RowCount:=1;       //a keresési eredménylista nullázása

A keresés helyétől függően beállítjuk, hogy honnan keressünk:

    if elorol1rb.Checked then        CurrTelAtFind := mainform.LastActiveMDIChild.TelefonKonyv;            //előröl keresünk     if hatulrol1rb.Checked then     begin //hátulról keresünk       CurrTelAtFind:=mainform.LastActiveMDIChild.TelefonKonyv;       while CurrTelAtFind.Kovetkezo <> nil do      begin         CurrTelAtFind:=CurrTelAtFind.Kovetkezo;         Inc(SrcNo);      end;     end;     if innen1rb.Checked then     begin //aktuális ponttól keresünk       CurrTelAtFind:=mainform.LastActiveMDIChild.TelefonKonyv;           //CurrTK       SrcNo:=telkonyv_unit.Aktualis;       for i:=1 to SrcNo-1 do         CurrTelAtFind:=CurrTelAtFind.Kovetkezo;     end;

A keresésnél a fő-form egyik mezőjéből a

LastActiveMDIChild
tulajdonságból, avagy az utolsó aktív telefonkönyv form-ból vesszük telefonkönyvet, az adatokat tartalmazó láncoltlistát. Hátulról keresés esetén elindulunk az első bejegyzéstől, és addig ugrálunk, amíg nincs következő bejegyzés, az-az vége a láncoltlistának. Az aktuális telefonkönyvet pedig a telkonyv_unit Aktualis változójából vesszük. Az SrcNo változó jelenti, hogy hányadik adatlapról van szó.

Ezek után a megadott keresendő szöveget szavaira bontjuk (minden helyközzel elválasztott karaktersorozatot külön szónak értékelünk). A szavakat egy tömbben tároljuk el, a szavak számát (a tömb maximális indexét) pedig egy Integerben:
 
 

FindTextNo:=0;  //0 a szavak száma KerSzoveg:=KerSzoveg+' ';            //a keresett szöveg + egy space, hogyha menjen.. while Pos(' ', KerSzoveg) > 0 do            //a keresés addig megy, míg space vanbegin   Inc(FindTextNo); //a szavak számának növelése   FindTextA[FindTextNo]:=Copy(KerSzoveg, 1, Pos(' ', KerSzoveg)-1);   //az adott szót átmásolja a tömbbe   Delete(KerSzoveg, 1, Pos(' ', KerSzoveg));   //az átmásolt szó törléseend; KerSzoveg:=TempSzoveg;  //a

Innen a keresés két különböző szálon fut tovább attól függően, hogy csak a keresett szóval telesen megegyező találatot keresünk, vagy szavankénti résztalálatot is. A különbséget "Csak ha a megadott szöveg a teljes szó" kapcsolóval lehet megállapítani, ez a wholecbox nevű checkbox Checked metódusával kérdezhető le.
 
 

if Not wholecbox.Checked then // szavak szerinti keresés while CurrTelAtFind <> nil do begin //maga a kereső rutin, szavak szerinti keresés esetén//maga a kereső rutin, a ciklus magja end;

A fenti ciklus egészen addig megy, míg a telefonkönyv egy adatlapját reprezentáló CurrTelAtFind (pointer) változó értéke nem nil. Az alábbiakban a ciklus magja:
 
 

VanTalalat:=False; if vnevcbox.Checked then FindWordInText(CurrTelAtFind.Nev.VezetekNev, SrcNo, 'Vezetéknév'); if knevcbox.Checked and Not (VanTalalat and distinctcbox.Checked) then FindWordInText(CurrTelAtFind.Nev.KeresztNev, SrcNo, 'Keresztnév'); if varoscbox.Checked and Not (VanTalalat and distinctcbox.Checked) then FindWordInText(CurrTelAtFind.Cim.Varos, SrcNo, 'Város'); if utcacbox.Checked and Not (VanTalalat and distinctcbox.Checked) then FindWordInText(CurrTelAtFind.Cim.Utca, SrcNo, 'Utca');for i:=1 to 10 do  // Telefonszám keresése a telefonszámok tömbben   if telcbox.Checked and Not (VanTalalat and distinctcbox.Checked)   then FindWordInText(CurrTelAtFind.Kapcs.Telefon[i], SrcNo, 'Telefon/' + IntToStr(i)); for i:=1 to 5 do   // email cím keresése az email tömbben   if emailcbox.Checked and Not (VanTalalat and distinctcbox.Checked)   then FindWordInText(CurrTelAtFind.Kapcs.email[i], SrcNo, 'email/' + IntToStr(i)); for i:=1 to 10 do  // WEB cím keresése a WEB-cím tömbben   if webcbox.Checked and Not (VanTalalat and distinctcbox.Checked)   then FindWordInText(CurrTelAtFind.Kapcs.WEB[i], SrcNo, 'WEB/' + IntToStr(i)); if Not hatulrol1rb.Checked // a következő adatlap megcímzése then begin CurrTelAtFind:=CurrTelAtFind.Kovetkezo; Inc(SrcNo); end      //előrefelé keresés esetén a következő adatlap else begin CurrTelAtFind:=CurrTelAtFind.Elozo; Dec(SrcNo); end;
     //hátrafelé keresés esetén az előző adatlap

A ciklus magja valójában csak a szükséges összehasonlító rutint hívja meg és ez a rutin egyben, ha találat van, eltárolja a keresés eredményét. Látható, hogy csak akkor hívódik meg elsődlegesen az adott mezőhöz tartozó összehasonlító rutin, ha az ki van jelölve (pl. vnevcbox.Checked). A továbbiakban még egy feltételt láthatunk: az azonos adatlapokhoz tartozó találatok kiszűrése. Ezt az "Azonos személy találatainak kiszűrése" kapcsolóval a distinctcbox checkbox vizsgálatával figyelhetjük le. Avagy: ha ez a kapcsoló bekapcsolt, akkor csak az első találatot kell rögzítenünk, így a VanTalalat Boolean változó értéke igaz és ezáltal nem jut be a többi mező vizsgálatánál az összehasonlító rutinba.

Az összehasonlító rutinnak, a FindWordInText() rutinnak három paramétere van, ezek közül az első a legfontosabb: az átadott összehasonlítandó adatlap mező értéke. A telefon, email és web cím vizsgálatánál ciklust kell alkalmaznunk lévén több (rendre 10, 5, 10) címet tárolhatunk el. Persze az kereső rutin meghívására itt is igazak a fent elmondottak.

A ciklusmag végén található a következő adatlap megcímzése - a láncolt listában történő lépegetés - attól függően, hogy előre- vagy hátrafelé haladunk a keresés során. Ha előrefelé haladunk, akkor az adatlap - CurrTelAtFind változó - Kovetkezo mezőjével, avagy következő rekordra mutató mezővel tesszük egyenlővé magát az adatlapot mutató változót, továbbá növeljük eggyel a láncolt listában az aktuális tartózkodási sorszámot a SrcNo változót (erre majd a kereső rutinnak lesz szüksége kiíratáskor). Ha hátrafelé haladunk a dolog pont fordítva játszódik le: az előző elemre mutató mező értékét használjuk és csökkentjük az aktuális pozíciót.

A ciklus ezek után kezdődik előröl míg, a vizsgálandó adatlap nem nil, avagy nincs tövább vizsgálandó adatlap. De lássuk magát a kereső rutint, mely most szavanként keres:
 
 

procedure Tfinddialog.FindWordInText(Text: String;                  SrcNo: Integer; MezoNev: String);var   wordno     : Integer;   VanTalalatL: Boolean;   OldText    : String; begin //az átadott stringben a keresendő szavak keresése   OldText:=Text;   if ekezetekcbox.Checked   then Text:=UnAccenter(Text);   //ékezettelenítés   if Not kisbnagybcbox.Checked   then Text:=LowerCase(Text);    //kisbetűsítés   for wordno:=1 to FindTextNo do  begin     VanTalalatL:=Pos(FindTextA[wordno], Text) = 1;     //egyszerű keresés: elég, ha a keresett szóval kezdődik a string     VanTalalat:=VanTalalatL;     if VanTalalatL then     //ha van találat, akkor az adatlapot felvesszük az eredmény listába       with telkonyv_kereses_eredmeny,kerlista do      begin         RowCount:=RowCount+1;         Rows[RowCount-1].Clear;         Rows[RowCount-1].Add(IntToStr(SrcNo));         Rows[RowCount-1].Add(MezoNev);         Rows[RowCount-1].Add(OldText);      end;     if VanTalalatL and Not distinctcbox.Checked then Break;     //ha van találat és ki akarjuk szűrni az azonosakat, akkor most kilépünk  end; end;

A kereső rutin is természetesen a paraméterben átadott, összehasonlítandó szöveg kisbetűsítésével és/vagy ékezettelenítésével kezd. Majd szavanként hasonlítjuk össze a keresési szóként megadott sztringet a paraméterben szereplő adatlap egyik mezőjével. A keresési sztringként megadott szöveget a szavakrabontott FindTextA[] többől veszi és a Pos() függvényt használja összehasonlításra. A keresés feltétele, hogy a szó az adatlap megadott mezőjének elején legyen, avagy a Pos() függvény egyet adjon vissza. Ezt azért így csináljuk, hogy elkerüljük a sok találatot a kis keresési kulcs megadása esetén. Persze ha nem csak a keresési kulccsal kezdődő találatra vagyunk kíváncsiak, akkor elég ha a Pos() függvény 0-nál nagyobbat ad vissza.

Végül is, ha volt találat, akkor a találatot kijelző telkonyv_kereses_eredmeny form kerlista TStringGrid objektumába bejegyezzük a talált adatlap jellelmzőjét. A bejegyzés során a Rows[]: TStrings tulajdonságot használjuk, amit sima TStrings objektumként kezelhetünk. (Clear, Add ...) Végül, ha van találat és csak az elsőt kell kijelezni, akkor kiugrunk a kereső ciklusból. Így a kereső rutin nem csak megkereste, hanem a találatot be is jegyezte.

Térjünk oda vissza, hogy mi van akkor "Csak ha a megadott szöveg a teljes szó" kapcsolót bekapcsoltuk, avagy a keresési szöveggel teljesen megegyező elemeket keressük az adatlapokban. Az alábbi kódrészlet valójában nagyon hasonlít a szavankénti kereséshez, csupán maga a kereső rutin más ( FindTextinText() ):
 
 

else   begin //csak a teljes szó keresése   while CurrTelAtFind <> nil do   begin //maga a kereső rutin, sztringek szerinti keresés esetén     VanTalalat:=False;     if vnevcbox.Checked     then FindTextInText(CurrTelAtFind.Nev.VezetekNev, SrcNo, 'Vezetéknév');     if knevcbox.Checked and Not (VanTalalat and distinctcbox.Checked)     then FindTextInText(CurrTelAtFind.Nev.KeresztNev, SrcNo, 'Keresztnév');     if varoscbox.Checked and Not (VanTalalat and distinctcbox.Checked)     then FindTextInText(CurrTelAtFind.Cim.Varos, SrcNo, 'Város');     if utcacbox.Checked and Not (VanTalalat and distinctcbox.Checked)     then FindTextInText(CurrTelAtFind.Cim.Utca, SrcNo, 'Utca');    for i:=1 to 10 do // Telefonszám keresése a telefonszámok tömbben       if telcbox.Checked and Not (VanTalalat and distinctcbox.Checked)       then FindTextInText(CurrTelAtFind.Kapcs.Telefon[i],                    SrcNo, Telefon/'+IntToStr(i));     for i:=1 to 5 do   // email cím keresése az email tömbben       if emailcbox.Checked and Not (VanTalalat and distinctcbox.Checked)       then FindTextInText(CurrTelAtFind.Kapcs.email[i],                    SrcNo, 'email/'+IntToStr(i));     for i:=1 to 10 do // WEB cím keresése a WEB-cím tömbben       if webcbox.Checked and Not (VanTalalat and distinctcbox.Checked)       then FindTextInText(CurrTelAtFind.Kapcs.WEB[i],                    SrcNo, 'WEB/'+IntToStr(i));     if Not hatulrol1rb.Checked  // a következő adatlap megcímzése     then begin CurrTelAtFind:=CurrTelAtFind.Kovetkezo; Inc(SrcNo); end     //előrefelé keresés esetén a következő adatlap     else begin CurrTelAtFind:=CurrTelAtFind.Elozo; Dec(SrcNo); end;     //hátrafelé keresés esetén az előző adatlap  end; end; // itt a vége: if Not wholecbox.Checked then

Mivel a fent - a szavankénti keresésnél - elmondottak tökéletesen ide is illenek, tehát a keresési ciklus mag a teljesen megegyező sztringek keresésénél is szinte ugyanaz nem is írnám le újra. Ellenben szükséges magát a keresőrutint kicsit áttekinteni. Persze ez se mutat óriási különbséget az előzőekhez képest:
 
 

procedure
Tfinddialog.FindTextInText(Text: String;                              SrcNo: Integer; MezoNev: String);var   VanTalalatL: Boolean;   OldText    : String;begin   OldText:=Text;   if ekezetekcbox.Checked then Text:=UnAccenter(Text);     //ékezettelenítés   if Not kisbnagybcbox.Checked then Text:=LowerCase(Text); //kisbetűsítés   if Length(KerSzoveg) = Length(Text)  //speed up comparation   then VanTalalatL:=KerSzoveg = Text   // teljes összehasonlítás   else VanTalalatL:=False;   VanTalalat:=VanTalalatL;   if VanTalalatL then   //ha van találat, akkor az adatlapot felvesszük az eredmény listába     with telkonyv_kereses_eredmeny,kerlista do    begin       RowCount:=RowCount+1;       Rows[RowCount-1].Clear;       Rows[RowCount-1].Add(IntToStr(SrcNo));       Rows[RowCount-1].Add(MezoNev);       Rows[RowCount-1].Add(OldText);    end; end;

A lényegbeli különbséget emelném itt is csak ki - nem untatva az Olvasót a hasonlóságokkal -, tehát a VanTalalatL Boolean változó akkor igaz, ha teljesen megegyezik a két sztring, a paraméterként átadott és a keresési sztring KerSzoveg. Ezek után, ha van találat, hasonlóan a kerlista TstringGrid-be felvisszük az eredményt.

Nos ezzel le is zárhatjuk az egyszerű keresést, lássuk a:

A részletes keresés

A részletes keresés se ördöngösebb probléma, csupán az összes mezőt össze kell hasonlítani, hogy megegyeznek -e. Itt a részletes keresés fülnél (aldialógus) ismertetett kigyűjtést ismertetem:
 
 

procedure Tfinddialog.kigyujt2btnClick(Sender: TObject); var   LocBool  : Boolean;   i, SrcNo : Integer;   vnevl, knevl, enevl, varosl, irszl, utcal,   hazszl, tell, emaill, webl, mezonev: String[64];begin //Részletes keresés   vnevl:=vnev.Text;   // a keresendő Vezetéknév átadása   knevl:=knev.Text;   // a keresendő Keresztnév átadása   enevl:=enev.Text;   // a keresendő Egyéb-név átadása   varosl:=varos.Text; // a keresendő Város névének átadása   irszl:=irsz.Text;   // a keresendő irányítószám átadása   utcal:=utca.text;   // a keresendő utca nevének átadása   hazszl:=hazsz.text; // a keresendő házszám átadása   tell:=tel.text;     // a keresendő telefonszám átadása   emaill:=email.text; // a keresendő email cím átadása   webl:=web.text;     // a keresendő web cím átadása   SrcNo:=1;   telkonyv_kereses_eredmeny.kerlista.RowCount:=1;   if elorol1rb.Checked then CurrTelAtFind :=         mainform.LastActiveMDIChild.TelefonKonyv;   if hatulrol1rb.Checked then   begin     CurrTelAtFind:=mainform.LastActiveMDIChild.TelefonKonyv;     while CurrTelAtFind.Kovetkezo <> nil do     begin       CurrTelAtFind:=CurrTelAtFind.Kovetkezo;       Inc(SrcNo);     end;   end;   if innen1rb.Checked then   begin     CurrTelAtFind := mainform.LastActiveMDIChild.TelefonKonyv;     SrcNo:=telkonyv_unit.Aktualis;     for i:=1 to SrcNo-1 do       CurrTelAtFind:=CurrTelAtFind.Kovetkezo;   end;   VanTalalat:=False;   while CurrTelAtFind <> nil do   begin  //maga a kereső rutin részletes keresés esetén     VanTalalat:=True;        //a kezdőérték itt True, mert és kapcsolat van az alábbiakban     MezoNev:='';     if vnevl <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Nev.VezetekNev, vnevl);              MezoNev:='V';     end;     if knevl <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Nev.KeresztNev, knevl);              MezoNev:=MezoNev+'K';     end;     if enevl <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Nev.Prefixumok, enevl);              MezoNev:=MezoNev+'E';     end;     if varosl <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Cim.Varos, varosl);              MezoNev:=MezoNev+'V';     end;     if irszl <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Cim.Irszam, irszl);              MezoNev:=MezoNev+'I';     end;     if utcal <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Cim.Utca, utcal);              MezoNev:=MezoNev+'U';     end;     if hazszl <> '' then begin              VanTalalat:=VanTalalat and              CompareToString(CurrTelAtFind.Cim.Hazsz, hazszl);              MezoNev:=MezoNev+'H';     end;     if tell <> '' then     begin       LocBool:=False;       for i:=1 to 10 do         LocBool:=LocBool or             CompareToString(CurrTelAtFind.Kapcs.Telefon[i], tell);       VanTalalat:=VanTalalat and LocBool;       MezoNev:=MezoNev+'t';     end;     if emaill <> '' then     begin       LocBool:=False;       for i:=1 to 5 do         LocBool:=LocBool or             CompareToString(CurrTelAtFind.Kapcs.email[i], emaill);       VanTalalat:=VanTalalat and LocBool;       MezoNev:=MezoNev+'e';     end;     if webl <> '' then     begin       LocBool:=False;       for i:=1 to 10 do         LocBool:=LocBool or                CompareToString(CurrTelAtFind.Kapcs.web[i], webl);       VanTalalat:=VanTalalat and LocBool;       MezoNev:=MezoNev+'w';     end;     if VanTalalat then       with telkonyv_kereses_eredmeny, kerlista do       begin         RowCount:=RowCount+1;         Rows[RowCount-1].Clear;         Rows[RowCount-1].Add(IntToStr(SrcNo));         Rows[RowCount-1].Add(MezoNev);         Rows[RowCount-1].Add(CurrTelAtFind.Nev.VezetekNev+' '+                              CurrTelAtFind.Nev.KeresztNev);       end;     if Not hatulrol1rb.Checked   // a következő adatlap megcímzése     then begin CurrTelAtFind:=CurrTelAtFind.Kovetkezo; Inc(SrcNo); end     //előrefelé keresés esetén a következő adatlap     else begin CurrTelAtFind:=CurrTelAtFind.Elozo; Dec(SrcNo); end;     //hátrafelé keresés esetén az előző adatlap   end;   with telkonyv_kereses_eredmeny, kerlista do   begin     if RowCount > 1 then FixedRows:=1;     if RowCount > 1     then telkonyv_kereses_eredmeny.show  //a keresés eredménye, listában     else Application.MessageBox('Nem találtam a keresési '+          'feltételeknek megfelelő adatlapot !',          'Információ', MB_OK or mb_IconInformation);   end; end;

Nos a rutin nem olyan elrémisztő amilyen hosszúnak látszik. Először nyilván helyi sztring változókban mentjük a keresési mintákat, a későbbi felhasználás végett. Majd a fenti kereső rutinokhoz hasonlóan beállítjuk a keresés irányát, és az első kereséshez szükséges adatlapot (CurrTelAtFind). Itt hasonlóan jön a ciklus mely magát, a keresést végrehajtja. A VanTalalat Booelan változót Igaz értékre állítjuk, hogy a továbbiakban és művelettel tudjunk az új logikai értékeket (hogy mindegyik mező összehasonlításkor igaz-e) hozzá és-elni.

Majd azokat a mezőket vizsgáljuk, amelyek nem üresek. Itt egy sima sztring összehasonlító függvényt hívunk meg, amit alább részletezünk. Erről most annyit kell tudni, hogy paramétere a két összehasonlítandó sztring és igaz értéket ad vissza, ha egyenlőek. Tehát valami hasonló logikai sort kapunk egy összehasonlítás során:

VanTalalat:=True és Megegyezik_a_Vezeteknev és Megegyezik_a_Keresztnev; //stb.

Így bármelyik vizsgált mező eltér, akkor nyílván nincs találat. A telefon, email és web címek vizsgálata másképp alakul, mert ott több pl. telefonszám van amik közül csak egynek kell megegyezni, nem az összes számnak kell a keresetnek lennie. Tehát az egyes telefonszámok között vagy kapcsolatot használunk és ekkor megkapjuk, hogy volt -e olyan telefon szám ami a keresésnek megfelel. Ezt az értéket (vagy volt, vagy nem: igaz / hamis) hozzuk és kapcsolatba a VanTalalat változóval. Hasonlóan vizsgáljuk az email és web címeket is.

Végül, ha volt találat, akkor a már fent ismertetett módon a találatot kijelezzük a kerlista TStringGrid-ben. A ciklusmag utolsó sora, pedig az új adatlap megcímzése.

Még észrevehettük MezoNev változó variálását, ez jelzi majd ki, hogy végül is mely mezőkben kerestünk (ami nem volt üres). Továbbá az SrcNo változó szerepe itt is ugyanaz kijelzi, hogy melyik rekordról van szó.

A fő függvény végén pedig megvizsgáljuk (RowCount), hogy adtunk -e új sort a keresés eredménye listához, mert ha nem kiírjuk, hogy nem találtunk semmit. Most dióhéjban ismertetem a saját CompareToString() metódust mely két sztringet hasonlít össze:
 
 

function  Tfinddialog.CompareToString(Str, SubStr: String): Boolean; var RetBool: Boolean; begin //ha egyenlő a két String akkor, True, egyébként nem   if ekezetekcbox.Checked   then begin Str:=UnAccenter(Str); SubStr:=UnAccenter(SubStr); end;   if Not kisbnagybcbox.Checked   then begin Str:=LowerCase(Str); SubStr:=LowerCase(SubStr); end;   if Not wholecbox.Checked           // vizsgálat: a megadott szöveg (SubStr) a teljes szó   then RetBool:= Pos(SubStr, Str) = 1           //elég, ha SubStr-rel kezdődik az Str string   else RetBool:= Str = SubStr;           //Akkor igaz, ha Str teljesen egyenlő SubStr   CompareToString:=RetBool; end;

Itt valóban simán csak a két paraméterként megadott sztringet vizsgáljuk meg. Természetesen a paraméterben átadott, összehasonlítandó sztring kisbetűsítésével és/vagy ékezettelenítésével kezd. Majd ha a "Csak ha a megadott szöveg a teljes szó" kapcsoló bekapcsolt, akkor feltétlenül meg kell egyeznie a keresett és talált szövegnek (egyenlőek), míg kikapcsolt állapotában elég, ha csak azzal kezdődik (a Pos() függvény egyet ad vissza). Az összehasonlítás eredményét a függvény egy logikai értékként adja vissza, amit a fenti metódusban használunk, a VanTalalat változó értékének kialakításához.

Az eredmény megjelenítése

Cikkünk végére csak eme apróság maradt. Fent már a megjelenítéshez szükséges form-ot elkészítettük, így most csak a vele kapcsolatos teendőket kell áttekinteni dióhéjban:

  • A form-nak megfelelően kell viselkednie, ha egyszerű, és ha részletes keresés van.
  • Ki kell írnia mely mezőkben kerestünk.
  • Navigálni kell gombokkal előre, illetve hátra az eredményt reprezentáló listában
  • Végül, ami a legfontosabb: meg kell jeleníteni a kiválasztott keresési eredményt, avagy az adatlapot.
Az eredményt megjelenítő rutinnak tudnia kell, hogy egyszerű, avagy részletes keresés eredményét jeleníti meg, mert másképp kell a mező nevek kijelzését és a szómintát megadnia. A rutin azt kérdezi le, hogy melyik TabSheet aktív, avagy a TPageControl melyik oldala aktív:
if finddialog.PageControl1.ActivePage.PageIndex = 0 then

Ezzel az első, tehát az egyszerű keresést detektálhatjuk.

A megjelenítő panelen található navigáló gomboknak nem csak egyszerűen navigálnia kell, hanem a navigálás során megfelelően kell módosítania a lista aktuális elemét:
 
 

procedure Ttelkonyv_kereses_eredmeny.kovbtnClick(Sender: TObject); var hol: TGridRect; begin   hol:=kerlista.Selection;   if hol.Top < kerlista.RowCount-1 then   begin     with hol do begin Inc(Top); Inc(Bottom); end;     kerlista.Selection:=hol;   end;   if mutatchbox.Checked then AdatokMutatasa; end;

Itt egyszerűen lekérdezzük mi, és hol van a listában kijelölve, és azt módosítjuk: ha a Következő gombot nyomjuk meg akkor nyílván eggyel lejjebbi sort jelölünk ki (ezt látjuk a fenti forráskód részletben), ha meg az Előző gombot akkor eggyel feljebb található sort, persze vigyázva ki ne lépjünk a listából.

Utolsó kedvencünk az AdatokMutatasa metódus mely a kiválasztott adatlapot megjeleníti:
 
 

procedure Ttelkonyv_kereses_eredmeny.AdatokMutatasa; var hol: TGridRect; begin   hol:=kerlista.Selection;   if kerlista.Cells[0, hol.top] <> 'Index' then   begin     mainform.LastActiveMDIChild.           UgrasAdottSzamura(StrToInt(kerlista.Cells[0, hol.top]));     mainform.LastActiveMDIChild.FeltoltPanelsWithActual;   end; end;

Itt csak lekérdezzük, hogy mi is volt utoljára kijelölve, majd a telefonykönyv form-ban - amit most a main-form LastActiveMDIChild mezője reprezentál - oda ugrunk, és megjelenítjük.

Lássuk az eredményt:

A kereséssel kapcsolatban ennyit tartottam feltétlenül fontosnak. Itt csak az elv, a megközelítés a fontos, így nem bonyolult és mindenre kiterjedő kereső algoritmust kreáltunk. Ami még fontos, hogy később meglátjuk, érdemesebb adatbázisokkal elkészíteni.

A forráskód és a lefordított EXE:
 
 

  Delphi-s változat Delphi nélkül is futó változat
Forráskód forrkod.zip Forrkodnodelphi.zip
EXE Telefonkönyv v0.5 Telefonkönyv v0.5 (Delphi nélkül)