Les 5: EuroCC: De Finale

 
EuroCC krijgt nog wat extra toeters en bellen.

We voegen een RadioGroup toe om het aantal decimalen in te stellen. We gaan de fouten opvangen die optreden als er een ongeldige waarde getypt wordt. Verder maken we het programma "internationaal" door rekening te houden met de decimal separator.

En tot slot voegen we een menu toe. Als dat niet de wereld op zijn kop is... ;-)

Voorbereidingen

  1. Als je dit nog niet gedaan hebt: download dan euronl03.zip naar \DelphiLa.
  2. Maak een nieuwe directory \DelphiLa\EuroNL03.
  3. Copieer euronl03.zip naar \DelphiLa\EuroNL03 en "unzip" het bestand in deze directory. Controleer of je de volgende bestanden hebt: euronl03.dpr, euroform.dfm en euroform.pas.

Je weet nu alvast reeds hoe je componenten op een "form" plaatst, hoe je hun properties instelt en hoe je "event handlers" schrijft. Dat sla ik dus gewoon over. In deze les bouw je gewoon verder op de EuroNL03-bestanden die je gedownload hebt.

Wat voegden we toe aan de Form?

Form1 werd aangevuld
  • In de radiogroup ValutaRadioG voegden we een item toe: FRF
  • Een label is toegevoegd voor "Franse Francs": FRFLabel
  • We voegden een Bevel-component toe, te vinden op de Additional pagina van het componentenpalet: Bevel1. Zijn property Shape wijzigden we van de standaardwaarde bsBox in bsFrame en we wijzigden zijn afmetingen zodat hij mooi als een kader rond het groepje labels staat.
    Tip: indien je een Bevel "rond" een aantal componenten plaatst, wordt het soms moeilijk om om die componenten zelf te selecteren door er gewoon op te klikken. Delphi denkt dan namelijk dat je klikte op de Bevel, omdat de Bevel "bovenop" die componenten ligt. In dat geval selecteer je de Bevel en geef je het commando send to background.
  • Een tweede radiogroep is toegevoegd: DecimRadioG. Zijn caption wijzigden we in Decimalen. Zijn items zijn:
       2
       4
    Om de Items naast elkaar te plaatsen (in plaats van de standaard verticale schikking), wijzigden we de property Columns van 1 naar 2.
    De ItemIndex zetten we op 0 (de eerste radiobutton is geselecteerd).

 

Wat is er gewijzigd in de broncode?

Laat ons nu eens de code in wat meer detail bekijken. Het merendeel van de programma-commentaren laat ik hier weg, maar de code die je gedownload hebt is nog altijd "fully commented" :)

  1. In de procedure ValutaConvert zie je om te beginnen de bijkomende code voor de munteenheid FRF.

    Wat meer opvalt: in de conversie naar Euro zijn de letterlijke waarden van de conversie-factoren verdwenen. We delen nu door cfBEF, cfFRF en cfNLG (in plaats van 40.3399, 6.55957 en 2.20371); "cf" staat voor "conversie factor".

    De conversion van Euro naar lokale valuta en het tonen van de resultaten delegeerden we naar de procedure ConvertShow. Het verbergen van het corresponderende label wordt nu gedaan door SetLabelInvisible. Daar kijken we straks even naar.
    procedure TForm1.ValutaConvert;
    var
      StartBedrag: real;
    begin
      StartBedrag := StrToFloat(InputEdit.Text);
      if ValutaRadioG.ItemIndex = 1 then
        Euro := StartBedrag / cfBEF
      else
        if ValutaRadioG.ItemIndex = 2 then 
          Euro := StartBedrag / cfFRF
        else
          if ValutaRadioG.ItemIndex = 3 then
            Euro := StartBedrag / cfNLG
          else
            Euro := StartBedrag;
      ConvertShow;
      SetLabelInvisible;
    end;
    
  2. De conversie-factoren zijn geen variabelen meer: we declareerden ze als constants. Bovendien kunnen deze constanten gebruikt worden door alle routines van de unit, omdat we ze declareerden in het de begin van de implementatie-sectie. Daarna wordt de globale variabele Euro gedeclareerd.
    implementation
    
    {$R *.DFM}
    
    const
      // Conversie-factoren voor diverse valuta
      // 1 EURO = X munteenheden
      cfBEF  = 40.3399;
      cfFRF  = 6.55957;
      cfNLG  = 2.20371;
    
    var
      // Globale variabele voor de waarde in Euro
      Euro: Real;
    
  3. De procedure ConvertShow maakt gebruik van een variabele FStr, waarin de "formatting string" geplaatst wordt (zie vorige lessen). Deze variabele zetten we op de gepaste waarde voor het tonen van 2 of 4 decimalen, afhankelijk van welke radiobutton er geselecteerd is in DecimRadioG. Kijk ook naar de globale variabele Euro en de globale constanten cfBEF, cfFRF en cfNLG (de conversie-factoren).
    procedure TForm1.ConvertShow;
    var
      FStr: string;
    begin
      if DecimRadioG.ItemIndex = 0 then
        FStr := '0.00'
      else
        FStr := '0.0000';
      EuroLabel.Caption := FormatFloat(FStr, Euro)         + ' EURO   ';
      BEFLabel.Caption  := FormatFloat(FStr, Euro * cfBEF) + ' BEF/LUF';
      FRFLabel.Caption  := FormatFloat(FStr, Euro * cfFRF) + ' FRF    ';
      NLGLabel.Caption  := FormatFloat(FStr, Euro * cfNLG) + ' NLG    ';
    end;
    
  4. Niks speciaals in SetLabelInvisible. Hier zie je nog steeds de techniek die we introduceerden in les 4.
    procedure TForm1.SetLabelInvisible;
    begin
      EuroLabel.Visible := (ValutaRadioG.ItemIndex <> 0);
      BEFLabel.Visible  := (ValutaRadioG.ItemIndex <> 1);
      FRFLabel.Visible  := (ValutaRadioG.ItemIndex <> 2);
      NLGLabel.Visible  := (ValutaRadioG.ItemIndex <> 3);
    end;
    

"CASE" ter vervanging van de geneste IF's

De reeks geneste if...then...else... opdrachten in de berekening van de variable Euro zal vrij lang en complex worden wanneer we later code toevoegen voor extra munteenheden. In ons geval is het beter deze constructie te vervangen door een case opdracht, vrij vertaald: "in geval dat..." Daardoor wordt het programma veel leesbaarder (en tevens sneller, indien je de waarden van de selector in stijgende volgorde plaatst).
Een stukje code is duidelijker dan duizend woorden:

procedure TForm1.ValutaConvert;
var
  StartBedrag,    // bedrag te converteren naar Euro
  CFactor: real;  // conversiefactor
begin
  StartBedrag := StrToFloat(InputEdit.Text);
  case ValutaRadioG.ItemIndex of
    1:  CFactor := cfBEF;  
    2:  CFactor := cfFRF;
    3:  CFactor := cfNLG;
  else
    CFactor := 1;  // eerste radioknop is voor Euro 
  end;
  Euro := StartBedrag / CFactor;
  // ...enzovoort...

Het case commando bevat een uitdrukking (de selector uitdrukking) die een waarde van het ordinale type moet opleveren. De twee bekendste ordinale types zijn integer (geheel getal) en char (character= letterteken), maar er zijn er nog wel wat andere.
Dan volgt er een lijst met mogelijke waarden voor de selector. Elke lijn bestaat uit een waarde, ofwel een lijst van waarden, ofwel een interval ("range") van waarden, ofwel een combinatie van al het voorgaande. De waarden in de lijst moeten constanten zijn en daarbij moeten ze ook uniek zijn.
Elke "mogelijkheid" wordt gevolgd door een statement (opdracht).
Op het einde van de case-constructie kan er eventueel een else-opdracht komen, die alle "gevallen" afhandelt die in de vorige regels niet vermeld werden.

Een ander voorbeeld van hoe zo'n case er uit kan zien:

var
  SelChar: char;  // dit is the selector
begin
  case SelChar of
    'a'               : statement1;  
    'b'               : statement2;
    'c', 'C', 'd'     : statement3; // LIJST van waarden
    'e'..'j'          : statement4; // RANGE van waarden 
    'k', 'K', 'l'..'z': statement5; // LIJST en RANGE 
  end;
  ...

Elk "statement" in de lijst mag, in plaats van een enkele opdracht, ook uit een blok van opdrachten bestaan, die genest worden tussen de gereserveerde woorden begin en end.

Afhandelen van de "input errors"

Indien de gebruiker een ongeldige (tekst-) waarde in de Edit-box typt, zal er een fout optreden wanneer het programma die "string" probeert te converteren naar een numerieke waarde. Door gebruik te maken van een try...except constructie kunnen we die fout-conditie zelf afhandelen en onze eigen foutboodschap tonen. Dit onderdrukt Delphi's foutboodschap.
Wijzig de procedure ValutaConvert als volgt:

procedure TForm1.ValutaConvert;
var
  StartBedrag, CFactor: real;  
begin
  try
    StartBedrag:= StrToFloat(InputEdit.Text);
    case ValutaRadioG.ItemIndex of
      1:  CFactor := cfBEF;
      2:  CFactor := cfFRF;
      3:  CFactor := cfNLG;
    else
      CFactor := 1;
    end;
    Euro := StartBedrag / CFactor;
    ConvertShow;
    SetLabelInvisible;
  except
    { Indien er een conversie-fout optreedt, komen we
      hier terecht, ONMIDDELIJK na de mislukte poging
      tot uitvoeren van de functie StrToFloat()       }
    on EConvertError do
      MessageDlg('Ongeldige invoer!', mtWarning, [mbOk], 0);
  end;
end;

Wanneer het volgende "statement" mislukt omdat er een ongeldige tekst in de Edit-box ingevoerd werd:
StartBedrag := StrToFloat(InputEdit.Text)

    ...springt het programma naar het except gedeelte, zonder de tussenliggende opdrachten nog uit te voeren. Daarna kijkt Delphi of er een "on" conditie staat in het except gedeelte die overeenkomt met de exception (uitzondering, fout) die optrad.

-  Indien ja, dan wordt de opdracht uitgevoerd die er op volgt.
-  Indien er geen overeenkomstige "exception" gevonden wordt in het except- blok, neemt Delphi zelf de touwtjes in handen en toont zijn eigen toepasselijke foutboodschap.
Daarna loopt het programma in de meeste gevallen verder (of alles loopt in het honderd, afhankelijk van het feit hoe goed de fout opgevangen werd...).

In ons geval kan er een "conversion-exception" optreden, en dan zal de "on EConvertError" conditie vervuld zijn. EConvertError is ingebouwd in Delphi, net als een hele hoop andere exceptions, zoals: EDivByZero, EZeroDivide, enzovoort... (kijk maar eens in Delphi's Help-systeem).

Punt of komma? Kijk naar de "decimal separator"

Indien we het programma laten rekening houden met Windows' decimale separator, hoeft de gebruiker zich geen zorgen te maken over het feit of hij nu een decimale punt of een komma moet typen. Desgevallend converteren we gewoon de komma tot een punt, of vice versa.

We doen dit in the OnKeyPress event handler van de Edit-box. Zoek de desbetreffende code op in de code-editor en vervolledig ze als volgt:

procedure TForm1.InputEditKeyPress(Sender: TObject; var Key: Char);
begin
  // de code voor de ENTER toets is 13
  if Key = #13 then
    ValutaConvert
  { DecimalSeparator is een GLOBALE variabele in Delphi
    die afhangt van de "Regional Settings" van Windows  }
  else
    if (Key = ',') and (DecimalSeparator = '.') then
      Key := '.'
    else
      if (Key = '.') and (DecimalSeparator = ',') then
        Key := ',';
end;

Dit betekent: wanneer component InputEdit de "focus" heeft en er wordt op een toets gedrukt:

Het programma uitbreiden voor meer valuta

Het is vrij eenvoudig om het programma uit te breiden met extra munteenheden: voeg gewoon wat extra globale constanten toe voor de conversie-factoren en voeg wat code toe aan de routines voor de berekeningen en het tonen van de resultaten. Dat kan nu vrij gemakkelijk, omdat we de routines reeds opgesplitst hebben.

Natuurlijk moet je ook de Items-property van de radiogroup voor de valuta uitbreiden. En je moet wat extra labels voorzien. Maar dat is gewoon routine ;-)

Hoe dit moet, kan je goed zien in de volledige versie van EuroCC, die je gedownload hebt in les 1.

We voegen een menu toe

Een eenvoudig programmaatje zoals EuroCC kan het best stellen zonder menu. Maar omwille van "educatieve redenen", en om Bill ;-) tevreden te stellen zodat we een "compliance-certificaat" kunnen krijgen, voegen we toch iets eenvoudigs toe.

Het menu zal slechts 1 "Bestand"-afrolmenu bevatten, met de volgende opties:
   2 decimalen
   4 decimalen
   een scheidingslijn ("separator")
   Exit

  1. Plaats een MainMenu component op de form: MainMenu1. Het icoon van deze component zal onzichtbaar zijn wanneer het programma uitgevoerd wordt ("at runtime"). Je zal dus alleen het resulterende menu zien, vlak onder de titelbalk van het programmavenster. Daarom is het onbelangrijk waar je de MainMenu component op de form plaatst.

    De MainMenu component
  2. Klik met de rechtermuisknop op de component MainMenu1. In het snelmenu dat geopend wordt, selecteer je Menu Designer. Je kan de Menu Designer trouwens ook openen door te dubbelklikken op de component MainMenu1.

    open de Menu Designer
  3. Je ziet nu een venster met een grafische voorstelling van je menu. Stel nu in de Object Inspector de eigenschappen in van het eerste MenuItem van MainMenu1:
       Caption: &Bestand
       Name: BestandMenu
  4. Klik ergens in de Menu Designer. In de grafische voorstelling van het menu verschijnt er een blokje onder Bestand. Dit stelt het eerste MenuItem voor dat je zal aanmaken onder het MenuItem BestandMenu. Klik op dat blokje.

    klik op het eerste blokje onder Bestand
  5. In de Object Inspector stel je de volgende eigenschappen in van je nieuwe MenuItem:
       Caption: &2 decimalen
       Checked: True
       Name: Bestand2Decim
    Druk daarna op Enter.
  6. Klik in de Object Inspector op de tab Events en dubbelklik naast OnClick. Je krijgt een "event handler" met de naam Bestand2DecimClick. In de Code Editor, vervolledig je Delphi's sjabloon als volgt:
    { Update de check marks van de menu-items,
      selecteer de eerste RADIOBUTTON van DecimRadioG
      en voer de conversie-routine uit                   }
    procedure TForm1.Bestand2DecimClick(Sender: TObject);
    begin
      Bestand2Decim.Checked := True;
      Bestand4Decim.Checked := False;
      DecimRadioG.ItemIndex := 0;
      ValutaConvert;
    end;
  7. Open opnieuw de Menu Designer. Klik in de rechthoek onder het MenuItem Bestand2Decim. In de Object Inspector stel je de properties in van het volgende MenuItem:
       Caption: &4 decimalen
       Name: Bestand4Decim
    Druk op Enter. Maak daarna een OnClick event handler aan, net zoals je deed voor het vorige MenuItem. In de editor vervolledig je Bestand4DecimClick als volgt:
    { Update de check marks van de menu-items,
      selecteer de tweede RADIOBUTTON van DecimRadioG
      en voer de conversie-routine uit                   }
    procedure TForm1.Bestand4DecimClick(Sender: TObject);
    begin
      Bestand2Decim.Checked := False;
      Bestand4Decim.Checked := True;
      DecimRadioG.ItemIndex := 1;
      ValutaConvert;
    end;
  8. Krijg je het beet? Oei oei... ik hoor sommige mensen vloeken tot hier... Een verkeerde klik en je gaat de mist in... Maar kom, de Menu Editor is nooit verder weg dan een dubbele muisklik op het icoontje van MainMenu1...

    Let's go. Voeg het volgende element toe aan het menu:
       Caption: - (ja, dat is een gewoon streepje, koppelteken, minteken als je wil)
       Name: BestandSeparator1
    en druk op ENTER.

    Hieraan moet je geen event handler koppelen, want dit wordt gewoon een scheidingslijn in het menu.
  9. Voeg het laatste element toe aan BestandMenu, met deze properties:
       Caption: &Exit
       Name: BestandExit
    en druk op ENTER. Indien je wil, kan je als caption ook &Sluiten opgeven om volledig in de stijl van een Nederlandstalig programma te blijven. Maar ik ben persoonlijk nogal voorstander van "Exit".

    In de Menu Designer zou je nu het volgdende moeten hebben:

    het finale menu
  10. Tot slot creer je een OnClick event handler voor het laatste MenuItem, die je als volgt aanvult:
    procedure TForm1.BestandExitClick(Sender: TObject);
    begin
      Close; // dit sluit Form1
    end;
    
    Wanneer je het hoofdvenster ("main form") van een applicatie sluit, dan wordt automatisch ook die applicatie afgesloten. En aangezien onze kleine applicatie slechts n form heeft...

Waarom staat dat & teken in de menu-captions? Wanneer zo'n teken voor een letter staat, wordt die letter onderstreept in het menu.

Misschien is je Form1 nu te klein geworden, omdat bovenaan een menu toegevoegd is. Dat merk je indien Delphi schuifbalken (scrollbars) aan je form toevoegde. In dat geval pas je simpelweg de afmetingen aan van de form, tot de schuifbalken verdwijnen.

Dat was het voor onze eerste serie lessen. Ik hoop dat jullie het even plezierig vonden als ik ;-)

En verder? Wel, waarom probeer je niet eens een andere "convertor" te programmeren, ditmaal niet voor valuta maar voor eenheden zoals afstand, gewicht, vermogen...?

De volgende groep lessen zal gaan over lijsten (lists), hoe je er door navigeert en hoe je items zoekt in een "list". We starten die tweede reeks met Acron, een programmaatje dat een lijst van acroniemen en hun betekenis toont en waarbij je acroniemen kan opzoeken via een "search".

Ondertussen ben je natuurlijk altijd welkom bij DelphiLand: indien je vragen of opmerkingen hebt over de lessen, bezoek dan zeker ons Forum!

[ TOP ]   [ HOME ]   [ GASTENBOEK ]

Een component naar de "background" sturen
In programmeurs-slang zeggen we: "de Z-volgorde van een grafisch object wijzigen". Grafische objecten kan je namelijk op elkaar plaatsen, zodat je "lagen" krijgt (layers). Je kan een visuele component naar de achtergrond sturen door hem te selecteren en daarna de optie Send to Back in het Edit menu te kiezen.
Het kan ook sneller: selecteer de component met de rechtermuisknop en kies Send to Back uit het speedmenu dat verschijnt.

© Copyright 1999 - 2007 
DelphiLand