
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