12. Tekst afbeelden

Vaak worden in applets een soort tabel of een aantal regels tekst afgebeeld.

Stel dat we de dagen van de week onder elkaar willen afbeelden. Dat zou je vrij eenvoudig kunnen doen op de volgende manier:

/* Eenvoudig programma om de
   dagen van de week te laten zien */

import java.applet.*;
import java.awt.*;
public class SimpeleDagen extends Applet {
   public void init ( ) {
     setBackground (Color.gray);
   }
   public void paint (Graphics g) {
      Font  groot = new Font ("Arial", Font.BOLD, 20);
      g.setFont (groot);
      g.setColor (Color.yellow);
      g.drawString ("maandag", 50, 40); 
      g.drawString ("dinsdag", 50, 70); 
      g.drawString ("woensdag", 50, 100); 
      g.drawString ("donderdag", 50, 130); 
      g.drawString ("vrijdag", 50, 160); 
      g.drawString ("zaterdag", 50, 190); 
      g.drawString ("zondag", 50, 220); 
   }
}

Dit programma is niet al te ingewikkeld. Het begint met commentaar -- wat je kunt zien aan de tekens /* en */.

     /* Eenvoudig programma om de
       dagen van de week te laten zien */

In de methode paint worden de woorden maandag tot en met zondag afgebeeld in gele letters op een grijze achtergrond (die in init is aangezet), dus zo:

   

Efficiënter behandeling van data

Dit programmaatje werkt wel, maar erg efficiënt is het niet.

Stel dat we geen zeven, maar enkele tientallen stuks data moeten afbeelden, dan zouden we ons programma toch echt iets anders moeten inrichten.

We kunnen de dagen van de week beter in een array plaatsen. De methode paint zou er dan zo gaan uitzien:

public void paint  (Graphics g) {
   String dagen[] = {"maan","dins","woens","donder",
        "vrij", "zater", "zon"};
     Font groot = new Font ("Arial", Font.BOLD, 20);
     g.setFont  (groot);
     g.setColor (Color.yellow);
     for (int  i=0;  i<7;  i++)
        g.drawString (dagen[i] + "dag",  50,  40 + i * 30);
}

De manier waarop de dagen worden afgebeeld werkt als volgt: de variabele i doorloopt via de for-lus de waarden 0 tot en met 6. De waarde van dagen[0] is "maan", en zo verder tot en met dagen[6] met als waarde "zon".

Elk van de dagen bevat het stukje "dag".

We kiezen ervoor dit niet zeven maal op te slaan, maar het pas aan de string te "plakken" als we die afbeelden.

Het programma bevat maar weinig data. Toch is het nu al iets korter geworden dan het eerste. Het is duidelijk dat met honderden stuks data de winst nog veel groter zal zijn.

Maar er is nog wel meer dat we aan het programma kunnen verbeteren.

Fontgrootte

In het algemeen zijn er twee manieren om een knop op te nemen in een programma. De eerste manier hebben we gezien in hoofdstuk 6, de geprefabriceerde Java-Button:

De tweede manier is dat we aan klikken op bepaalde vlakken binnen het appletvenster een bepaalde betekenis toekennen, bijvoorbeeld zo:

// een knop die wordt uitgelezen met MouseListener
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class BovenOfOnder extends Applet
                    implements MouseListener {
   int clickY=9999;

   public void init () {
      setBackground (Color.gray);
      addMouseListener (this);
   }

   public void paint (Graphics g) {
      Dimension d = getSize();
      int hoogte = d.height, breedte=d.width;
      g.setColor (Color.yellow);
      g.setFont (new Font ("Verdana",Font.BOLD, 16));
      g.drawLine(0,hoogte/2,breedte,hoogte/2);
      String dl = " de lijn.";
      if (clickY==9999)
         g.drawString ("klik boven of onder"+dl,20,hoogte/4);
      else if (clickY<hoogte/2)
         g.drawString ("Geklikt boven"+dl,20,hoogte/4);
      else if (clickY<hoogte/2)
         g.drawString ("Geklikt onder"+dl,20,3*hoogte/4);
      else
         g.drawString ("Geklikt op"+dl,20,hoogte/2);
   }

    public void mousePressed (MouseEvent evt) {
       clickY = evt.getY();
       repaint();
   }

   public void mouseReleased (MouseEvent evt){}
   public void mouseEntered (MouseEvent evt){}
   public void mouseExited (MouseEvent evt){}
   public void mouseClicked (MouseEvent evt){}
}

In paint wordt het venster door een rechte lijn in een onderste en een bovenste deel verdeeld.

Als geklikt wordt, vangt de methode evt.getY in mousePressed de y-coördinaat op. We wijzen deze toe aan de globale variabele clickY, zodat in paint kan worden geconstateerd of er boven, onder of op de lijn geklikt is. Het programma ziet er zo uit:

Vaak zul je op zo'n knop een tekst willen zetten. Maar dan moet zo'n tekst natuurlijk wel netjes passen, dus niet zo:

Het ziet er natuurlijk pas goed uit als zo'n tekst het grootste gedeelte van de breedte van de knop beslaat.

We kunnen een methode maken die zo'n knop voor ons afbeeldt. We gaan onze methode vijf argumenten meegeven:

  • x-coördinaat
  • y-coördinaat
  • breedte
  • hoogte
  • tekst

Behalve een methode om de knop neer te zetten, is het ook handig een tweede methode te maken (knopGeklikt), waarmee kan worden geconstateerd of op de knop geklikt is:

// Een methode om een knop met tekst neer te zetten
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class KnopMetTekst extends Applet
                      implements MouseListener {
int clickX, clickY, vBr, vHo; // vensterbreedte, -hoogte
boolean zetTekst = true;

public void init () {
   setBackground (Color.pink);
   addMouseListener (this);
}

void knop (int x, int y, int br, int ho, String s) {
   int fg=1, len;
   FontMetrics fm;
   Graphics g = getGraphics();
   g.setColor (Color.gray);
   g.fillRect (x+3, y+3, br, ho);
   g.setColor (Color.red);
   g.fillRect (x-3, y-3, br, ho);
   g.setColor (Color.black);
   g.drawRect (x-3, y-3, br, ho);
   do {
      g.setFont (new Font ("Verdana",Font.BOLD, fg++));
   } while (g.getFontMetrics().stringWidth(s)<br-14);
   fg--;
   g.setFont (new Font ("Verdana",Font.BOLD, fg));
   g.setColor (Color.yellow);
   len = g.getFontMetrics().stringWidth(s);
   g.drawString (s, x-3+br/2-len/2, y-3+ho/2+fg/2);
}

boolean knopGeklikt (int x, int y, int br, int ho) {
   if (clickX>x-3 && clickY<x-3+br && clickY>y-3 && clickY<y+ho)
   return true;
else
return false;
}

public void paint (Graphics g) {
   Dimension d = getSize();
   vHo = d.height; vBr=d.width;
   int strBr;
   knop (vBr/4, vHo/4, vBr/2, vHo/3, "klik hier");
   String txt[]={"Demo van stringWidth",
                     "(klasse: FontMetrics)"};
   if (knopGeklikt (vBr/4, vHo/4, vBr/2, vHo/3) && zetTekst) {
     for (int i=0; i<2; i++) {
         g.setColor (Color.black);
         g.setFont (new Font ("Verdana",Font.BOLD, 20));
         strBr = g.getFontMetrics().stringWidth(txt[i]);
         g.drawString(txt[i],vBr/2-strBr/2,(10+i*2)*vHo/13);
     }
   }
}

public void mousePressed (MouseEvent evt) {
   clickX = evt.getX();
   clickY = evt.getY();
   zetTekst = !zetTekst;
   repaint();
}

 public void mouseReleased (MouseEvent evt){}
  public void mouseEntered (MouseEvent evt){}
  public void mouseExited (MouseEvent evt){}
  public void mouseClicked (MouseEvent evt){}
}

Zodra er is geklikt, worden in mousePressed de x- en y-coördinaat toegekend aan de (globaal gedeclareerde) clickX en clickY.

Daarna krijgt de boolean-variabele zetTekst de tegengestelde waarde (het uitroepteken betekent: not). Met andere woorden, als hij true was, wordt hij false, en andersom.

Daarna wordt in paint een nieuwe knop getekend en met knopGeklikt gecontroleerd of er op het vlak van de knop geklikt is.

Ten slotte wordt er twee teksten neergezet als de variabele zetTekst gelijk is aan true.

   

Een string-array "inpakken"

We komen nog even terug op het eerste programma van dit hoofdstuk. De data kosten nogal wat ruimte. In de volgende programmaregel nemen ze 53 tekens in beslag, waarvan er maar liefst 22 uit komma¹s, aanhalingstekens en accolades bestaan. Dat is meer dan 40% van de ingetypte tekens.

String dagen[]={"maan","dins","woens","donder","vrij","zater","zon"};

We kunnen de dagen "verpakken" in een string waarin de afzonderlijke woorden gescheiden worden door slechts één teken, bijvoorbeeld zo:

String s="maan-dins-woens-donder-vrij-zater-zon";


Het is mogelijk deze string "uit te pakken" naar een array. Hiertoe maken we gebruik van de klasse StringTokenizer.

Om deze klasse te kunnen gebruiken moeten we boven de applet de volgende regel opnemen:

     import java.util.StringTokenizer;

Ook moeten we van deze klasse een object (in dit geval: st) aanmaken, waaraan we twee gegevens moeten meegeven: de naam van de uit te pakken string en het teken (streepje) waardoor de elementen gescheiden worden.

     StringTokenizer st = new StringTokenizer (s, "-");

De elementen worden op de volgende manier in het array gezet:

     String dagen[ ] = new String [7];
     int teller = 0;
     while (st.hasMoreTokens( ))  {
        dagen[teller++] = st.nextToken( );
     }

   

Een hele tekst afbeelden

De methode stringWidth is goed te gebruiken om een tekst passend maken binnen de ruimte die we ervoor hebben.

Als je veel tekst moet afbeelden is het handig om woord voor woord te bepalen of de tekst nog wel in de beschikbare ruimte past.

Dit gaan we doen in het laatste programma van dit hoofdstuk, waarin een tekst van enkele honderden woorden passend binnen het appletvenster wordt afgebeeld:

// Demonstratie van de klasse StringTokenizer
import java.applet.*;
import java.awt.*;
import java.util.StringTokenizer;
public class TekstAfbeelden extends Applet {
   public void init () {
      setBackground (Color.gray);
   }
   public void paint (Graphics g) {
      String s = "Het machinetaal-programma (de executa"+
         "ble) is natuurlijk afhankelijk van het soort s"+
         "ysteem waarop we het willen draaien: het bevat"+
         " instructies voor één bepaalde processor en he"+
         "t roept één specifiek besturingssysteem aan. Z"+
         "ulke programma's draaien slechts op één systee"+
         "m; een programma dat probleemloos draait onder"+
         " Windows is nutteloos onder andere systemen, z"+
         "oals Unix of MacOS. Het zou ook heel lastig zi"+
         "jn dit soort programma's te laten uitvoeren op"+
         " het Internet. Het Internet wordt immers bevol"+
         "kt door allerlei computersystemen. Wie zo'n (c"+
         "onventioneel) programma op z'n homepage zou wi"+
         "llen zetten, zou versies moeten neerzetten voo"+
         "r Linux, Windows en allerlei andere systemen. "+
         "Er is nog een tweede probleem: Internet-gebrui"+
         "kers kunnen niet riskeren onbekende programma'"+
         "s te downloaden en op hun systeem te laten uit"+
         "voeren. Een programma dat men via het Internet"+
         " in huis gehaald heeft, kan wel vrolijk de har"+
         "de schijf beginnen te formatteren. voor beide "+
          "problemen biedt Java de oplossing.";
      String woorden[ ] = new String[500];
      StringTokenizer st = new StringTokenizer (s," ");
      int aantal = 0;
      while (st.hasMoreTokens( )) {
         woorden[aantal++] = st.nextToken();
      }
      Dimension d = getSize();
      int vb=d.width, i=0, hoogte=16; // vb=vensterbreedte
      g.setColor (Color.yellow);
      g.setFont (new Font ("Arial", Font.PLAIN, 14));
      String regel = "";
      FontMetrics fm = g.getFontMetrics();
      while (i < aantal) {   // begin
         if (fm.stringWidth(regel+" "+woorden[i]) < 9*vb/10) {
            regel += " " + woorden[i];
            i++;
         }
         else {
            g.drawString (regel, vb/20, hoogte+=25);
            regel = "";
         }
      }
      g.drawString (regel, vb/20, hoogte+=25); // eind
   }
}

In het programma zouden we de string s ook als één lange regel kunnen schrijven, maar dan zou die buiten het venster van de programma-editor vallen, wat wel eens lastig kan zijn.

Het feitelijke proces vindt plaats tussen de regels die gemarkeerd zijn door //begin en //eind. Telkens wordt geprobeerd of een regel met het volgende woord erbij nog binnen 9/10 van de vensterbreedte vb past.

Past het, dan plakken we het woord achter de regel.

Wordt het te lang, dan beelden we de regel (zonder dat laatste woord) af, we verhogen de hoogte voor de volgende regel, waarna we met een lege regel beginnen.

Hoe dat er in de praktijk uit gaat zien, kun je hieronder bekijken:

Het is ook mogelijk de tekst geheel uit te lijnen. Het programma daarvoor is misschien wat te ingewikkeld om in deze cursus te behandelen. Voor wie er toch belangstelling voor heeft, zet ik hieronder het programmadeel waar de tekst zowel links als rechts uitgelijnd wordt:

Dimension d = getSize();
int vb = d.width, hoogte = 30; // vb=vensterbreedte
g.setColor (Color.yellow);
g.setFont (new Font ("Arial", Font.PLAIN, 18));
String regel = woorden[0];
int eerste=0, laatste=0, w, pos[] = new int[20], p=1;
pos[0] = 0;
int ruimte = 9*vb/10;
FontMetrics fm = g.getFontMetrics();
int index = 1;
while (index < aantal) {
   if (fm.stringWidth(regel+" "+woorden[index]) <= ruimte) {
      pos[p++] = fm.stringWidth(regel+" ");
      regel += " " + woorden[index];
      laatste = index;
      index++;
   }
   else {
      int begin = 1;
      for (int verschil=ruimte-fm.stringWidth(regel); verschil>0; verschil--) {
         if (pos[begin]==0)
            begin = 1;
            for (w=begin; pos[w]>0; w++)
               pos[w]++;
            begin++;
      }
      p = 0;
      for (w=eerste; w<=laatste; w++) {
         g.drawString (woorden[w], vb/20+pos[p], hoogte);
         p++;
      }
      regel = woorden[index];
      eerste = index;
      index++;
      for (p=0; p<pos.length; p++)
         pos[p]=0;
      hoogte+=25;
      p = 1;
   }
}
g.drawString (regel, vb/20, hoogte);

Ook hier zal ik laten zien hoe dat eruit ziet:

   

Opdracht 12.1

Schrijf een programma dat een zwarte rechthoek in het midden van het beeldscherm afbeeldt op een grijze achtergrond. Daarin komt je naam in gele letters, die er precies in passen. De breedte van je naam moet (in elke browser) tussen 40% en 50% van het appletvenster in beslag nemen, bijvoorbeeld zo:

   

Opdracht 12.2

Copyright-vermeldingen zie je vaak in kleine letters in de hoek van het venster staan. Breid de eerste opdracht zo uit dat een copyright-vermelding die zo'n 140 à 160 pixels breed is netjes in de rechterbenedenhoek komt, bijvoorbeeld zo:

   

Opdracht 12.3

Herschrijf opdracht 12.2 zo dat de fontgrootte van zowel de grote als de kleine tekst bepaald worden in een methode die de maximale fontgrootte bepaalt waarbij de tekst nog in de beschikbare ruimte past, en die er bijvoorbeeld zo uitziet:

     int bepaalFontGrootte (Graphics g, String tekst, int ruimte)

   

(c) 2003-2008, Thomas J.H.Luif