Bob Swart (aka Dr.Bob)
Een slimme listbox voor Kylix

Vorige keer liet ik zien hoe je in Delphi een TListBox component kunt uitbreiden met een editbox die kan helpen bij het snel zoeken (quick search) binnen de - gesorteerde - lijst van items in de listbox. Ik heb hierbij geprobeerd om de code zo cross-platform mogelijk te houden. Deze keer zullen we zien of me dat gelukt is. Daarnaast zal ik het positioneren van het huidige item wat efficienter laten werken, en we eindigen met een leuk en bruikbaar component voor zowel Delphi als Kylix.

Linux
Als eerste moeten we de source code unit TBLISTBX.PAS van Windows naar Linux zien te krijgen. De makkelijkste manier is om dit met een gewone floppy te doen, maar als op één van de machines een FTP server draait dan kan dat ook (en vind ik zelf eigenlijk nog net iets makkelijker). Wie echt een netwerkverbinding tussen een Linux en een Windows machine wil opzetten is aangewezen op Samba, wat niet echt eenvoudig op te zetten is. Maar goed, laten we aannemen dat het bestand TBLISTBX.PAS op de een of andere manier op de Linux machine is gekomen. En dat daar ook een versie van Kylix staat. De prijzen van Kylix zijn aan de hoge kant, maar op het moment dat dit nummer van Blaise verschijnt zal het niet lang meer duren voor de Open Editie van Kylix beschikbaar is. Als we TBLISTBX.PAS in Kylix openen, dan volgt meteen een foutmelding. De unit zelf heeft namelijk tblistbx als naam, en Linux - voor wie dat vergeten was - maakt gebruik van case-sensitive bestandsnamen. Dus moeten we er eerst tblistbx.pas van maken. Als we daarna de unit opnieuw openen, kunnen we hem niet meteen proberen te compileren (dat kan in Kylix - net als in Delphi - slechts met een project). Dus, start een nieuw project, en voeg de unit toe aan de use clause van de main form. Als we dan eindelijk op de Ctrl+F9 drukken om te compileren, volgen er een aantal foutmeldingen. We zien onder meer dat de units Controls en StdCtrls niet gevonden kunnen worden. Niet zo gek, want Controls en StdCtrls zijn VCL units. En de CLX equivalenten heten nu QControls en QStdCtrls (er verschijnt binnenkort een white paper waarin een lijst van Delphi VCL units en corresponderende Kylix CLX units staat).

IFDEFs
De beste manier om de verschillen tussen deze twee unitnamen op te lossen is door gebruik te maken van IFDEFs in de uses clause. In ons geval zouden die er als volgt uit moeten zien (voor Delphi 5 gebruik ik WIN32, voor Delphi 6 en hoger ook MSWINDOWS):

  uses
    Classes,
  {$IFDEF WIN32}
    Controls, StdCtrls;
  {$ENDIF}
  {$IFDEF LINUX}
    QControls, QStdCtrls;
  {$ENDIF}
Merk op dat ik geen {$ELSE} gebruik om te concluderen dat het alternatief voor WIN32 gelijk is aan LINUX. Borland geeft zelf namelijk al aan dat er op termijn misschien wel eens een derde platform kan komen waar Delphi op kan draaien. Delphi voor OS/2? Niet waarschijnlijk. Delphi voor MacOS? Ach, als de markt groot genoeg wordt. Misschien wel Delphi voor palmtops of wireless devices. Wie zal het zeggen? Zeker is dat áls er ooit een dergelijke versie van Delphi verschijnt, dat dan met zekerheid gezegd kan worden dat de {$ELSE} van WIN32 niet langer Linux is. En om dan weer al je code door te lopen is net zoiets als het bewust gebruik maken van twee cijfers om het jaartal weer te geven.

Hercompileren
Als we na het aanpassen van de uses clause het programma weer proberen te compileren, dan lukt dat zowaar. Zonder een enkele foutmelding of warning. Ik had toch op z'n minst nog wel enkele kleine problemen verwacht, maar het valt dus erg mee. Dan maar meteen de volgende stap: het installeren van de TBListBox component in Kylix via Component | Install Component. Ook dit gaat zonder problemen, en het programma van vorige keer is in enkele minuten te reproduceren onder Linux met Kylix:

Ook nu weer kunnen we snel zoeken in een lange lijst met items, waarbij we gebruik kunnen maken van meer dan alleen maar de eerste letter van de items in de listbox. Ik geef toe dat ik een klein beetje teleurgesteld ben in het gemak waarmee ik de component van Delphi naar Kylix kon omzetten. In komende nummers van Blaise zal ik proberen wat meer ingrijpende voorbeelden te bespreken (zoals het omzetten van een database toepassing - bedenk dat de BDE niet beschikbaar is onder Linux, en er daar met dbExpress gewerkt moet worden). Nu is het echter tijd om mijn andere belofte waar te maken: het versnellen van het zoekalgoritme zelf.

EditBoxNotifiesListBox
De implementatie van de methode EditBoxNotifiesListBox bestond uit een simpele while-loop die allereerst de lengte van de tekst in de editbox ophaalt (omdat we van de items in de listbox alleen maar dat stukje willen vergelijken dat net zolang is als de inhoud van de editbox), en vervolgens door de items in de listbox loopt. Dit is een lineair zoekalgoritme, dat langzaam kan worden als de lijst groot wordt. Om dit te demonstreren heb ik de volgende code geschreven die kan helpen om de listbox te vullen met 10.000 random strings (elk van 7 letters, waarbij de eerste een hoofdletter is):

  procedure TForm1.ItemsClick(Sender: TObject);
  var
    i,j: Integer;
    Str: String[7];
  begin
    for i:=1 to 10000 do
    begin
      Str := 'Aaaaaaa';
      for j:=1 to 6 do
        Inc(Str[j],Random(26));
      TBListBox1.Items.Add(Str)
    end
  end;
Gelukkig wordt het zoeken alleen maar uitgevoerd als de inhoud van de editbox veranderd, dus valt het potentieel snelheidprobleem wel mee. Maar bij een paar duizend items is wel te merken dat het intikken van een nieuwe letter pas na een ruime seconde tot een nieuwe positionering leidt. En juist in gevallen waarin de listbox zoveel items bevat is het slimme zoeken met de editbox zo handig, dus laten we een ons lineair zoekalgoritme veranderen in iets slimmers. Binair zoeken bijvoorbeeld. Waarbij we steeds de middelse string in de lijst vergelijken met de tekst in de editbox. Als de middelste string groter is, dan nemen we de eerste helft, anders de tweede helft, en zo net zolang tot we nog maar twee items over hebben (en de beste heeft dan gewonnen). Een listbox met 10.000 strings kunnen we nu in 14 stappen doorzoeken in plaats van 10.000 stappen. En ja, die extra 9.986 schelen relatief gezien een heleboel tijd. De nieuwe source code van het TBListBox component is te zien in onderstaande listing:
  procedure TBListBox.EditBoxNotifiesListbox(Sender: TObject);
  var
    len,index: Integer;
    min,max: Integer;
  begin
    len := Length(FEditBox.Text);
    min := 0;
    max := FListBox.Items.Count;
    repeat
      index := min + (max - min) div 2;
      if CompareText(FEditBox.Text,
           Copy(FListBox.Items[index],1,len)) > 0 then min := index
      else max := index;
    until (min >= Pred(max));
    if CompareText(FEditBox.Text,Copy(FListBox.Items[min],1,len)) = 0 then
      FListBox.ItemIndex := min
    else
      FListBox.ItemIndex := max
  end {EditBoxNotifiesListBox};
Alhoewel het er wat ingewikkelder uitziet, werkt het inderdaad een stuk sneller. En nog steeds zowel onder Windows (met Delphi) als Linux (met Kylix). Volgende keer eens kijken of we met Delphi 6 nog bijzonderheden moeten toevoegen. Want Delphi 6 bevat ondersteuning voor zowel VCL (de Controls en StdCtrls) als CLX (QControls en QStdCtrls), dus kan in feite op beide manieren de TBListBox gebruiken. Maar dat zien we een andere keer...

Kylix Open Edition
Medio zomer 2001 verwacht ik dat de Kylix Open Edition beschikbaar zal zijn. Helaas zal deze versie van Kylix niet alle componenten bevatten die in de Desktop en Server editie zitten. Met name op het gebied van internet toepassingen (CGI en Apache DSO) zal er geen of nauwelijks ondersteuning zijn. Toch is het wel mogelijk om ook dergelijke programma's met de Open Editie van Kylix te maken, zoals ik de volgende keer zal laten zien.


This webpage © 2001-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.