Hoofdstuk 5: Grafische gebruikersinterfaces
Inleiding
- Tegenwoordig zijn grafische gebruikersinterfaces niet meer weg te denken in een huidig programma. Via de
System.out.println
kunnen we textueel uitvoer geven aan de gebruiker, maar dit is achterhaald in huidige computersystemen die hoge-resolutieschermen, toetsenbord en een muis bevatten. - In dit hoofdstuk gaan we in op hoe we grafische gebruikersinterfaces of GUI's kunnen maken. We zullen ook merken dat dit een leuke toepassing is van Object-geörienteerd programmeren. Elke component in een GUI (een knop, een tekstvak, een venster) kan je immers gaan voorstellen als een object dat bepaalde eigenschappen heeft en bepaalde zaken kan uitvoeren. In Java worden deze componenten reeds voorzien van klasses met gegevensvariabelen en methodes.
Componenten, Layout en gebeurtenisafhandeling
- Een grafische interfcae wordt gebouwd door componenten op een beeldscherm te rangschikken. Componenten worden voorgesteld door objecten. Dit zijn dingen zoals knoppen, tekstvakken, menu's, aankruisvakjes,...
- De rangschikking van deze componenten kan gebeuren op basis van een structuur of lay-out. In Java zitten bepaalde lay-outmanagers die de schikking van de componenten automatisch kan doen.
- Wanneer er op een knop gedrukt wordt, dan moet er iets gebeuren. Die gebeurtenis zal een stuk code uitvoeren. Deze reactie op gebeurtenis wordt gebeurtenisafhandeling genoemd die door gebruikers geïnitieerd wordt, via bv. een muisklik of een toetsenbordinvoer.
AWT, Swing en JavaFX
Java heeft nu drie grafische-interfacebibliotheken. De oudste heet AWT (Abstract Window Toolkit) en werd geïntroduceerd als onderdeel van de eerste Java API. Daarna werd Swing geïntroduceerd en was een sterk verbeterde versie. De Derde en nieuwste bibliotheek is JavaFX en is een ganse herwerking van grafische-interfacebibliotheek omdat huidige systemen veel geavanceerder werden (applicatie op verschillende schermen, multitouch functionaliteit,...).
Wij zullen ons in die hoofdstuk bezighouden met Swing. De Swing bibliotheek herken je door het feit dat er veel klasses beginnen met de hoofdletter
J
(JButton
,JFrame
,...).
Grafische interfaces
- Grafische gebruikersinterfaces zijn heel belangrijk in programma's. We weten dat een scherm en een beeld opgedeeld is in pixels (Engels: picture elements). Elke pixel is een heel klein gebied dat een kleur kan hebben.
- De locatie van deze pixels wordt georganiseerd in een assenstelsel. Elk punt in Java kan men voorstellen via een (x,y) paar van waarden. In Java ligt de oorsprong (het punt met (0,0) van het assenstelsel in de linkerbovenhoek.
De klasse JFrame
- In een modern besturingssysteem wordt een programma geopend in een grafisch venster. Dit venster heeft enkele eigenschappen zoals breedte, hoogte en locatie. Een dergelijk venster kan vergroot of verkleind worden... In Java heten deze vensters frames en kunnen in Swing worden voorgesteld door de klasse
JFrame
. - Om een verster op het scherm weer te geven, moeten we een frame maken en tonen. Dit doen we door een instantie te creëren van
JFrame
. We kunnen via methodeaanroepen het object tonen op het scherm. - Hieronder vind je een voorbeeld hoe je de klasse
JFrame
gebruikt:
EenvoudigeGUI.java
- Een frame bestaat uit 3 onderdelen: de titelbalk, een optionele menubalk en het inhoudspaneel. De vorm van de titelbalk is afhankelijk van het onderliggend besturingssysteem. De menubalk en inhoudspaneel wordt gecontroleerd door de applicatie zelf. We kunnen aan het inhoudspaneel eenvoudige componenten toevoegen.
Eenvoudige componenten toevoegen
- Standaard staat een object van JFrame op onzichtbaar. We kunnen wel componenten toevoegen, bv een tekstvak (Eng. label) waarin we een stuk tekst plaatsen.
- Eerst wordt een verwijzing naar het inhoudspaneel opgehaald van het frame. Die wordt dan gebruikt om een label toe te voegen aan het inhoudspaneel, via de methode
add()
. - Het inhoudspaneel is van de klasse
Container
, dat is een klasse dat een verzameling van componenten bevat. - Wanneer we dit gedaan hebben, moeten we nog twee regels toevoegen:
via de methode
pack()
worden alle componenten netjes in het frame geschikt. De methodesetVisible()
verandert de zichtbaarheid van het frame, dat standaard op onzichtbaar is.Een andere component is een knop. In de Swing bibliotheek kunnen we dit aanmaken via
JButton
.
Alternatieve structuur
- In het vorige programma hebben we ervoor gekozen om het maken van de GUI te doen in een
main
-methode. Als alternatief kunnen we onze klasseEenvoudigeGUI
als een subklasse vanJFrame
beschouwen. Het is een programmeerstijl die heel vaak voorkomt. De creatie van het frame wordt dan gedaan in de constructor.
EenvoudigeGUI.java
EenvoudigeApp.java
De klasse JPanel
- Een object van het type
JPanel
is een container. Dit object kan niet getoond worden op zichzelf, maar moet in een andere container geplaatst worden. Meestal wordt een JPanel aan een JFrame toegevoegd via methodesetContentPane()
van de klasseJFrame
:
JFrame frame = new JFrame();
JPanel paneel = new JPanel();
frame.setContentPane(paneel);
LabelenKnopGUI.java
LabelenKnopApp.java
Via het
JPanel
kunnen we ook een layout meegeven. In Swing wordt de layout verzorgt door zogenaamde layoutmanagers, die gaan automatisch de plaatsing doen van onze componenten. Daarover straks meer.Gebeurtenisafhandeling
- Als je op een knop klikt of als je een tekst invult in een vak, dan kan dit een gebeurtenis genereren. Het javaprogramma moet dan de gebeurtenis afhandelen en duidelijk zeggen wat er dient te gebeuren. Als er een gebeurtenis plaatsvindt moet het programma daarop reageren. Dat noemt men gebeurtenisafhandeling of in het Engels event handling.
- In een objectgeoriënteerd programma verrichten objecten allerlei taken. De makers van Java weten echter niet wat er dient te gebeuren in jouw applicatie als de gebruiker op een knop drukt. Daarvoor hebben de makers van java een aparte klasse gemaakt die de afhandeling voor zijn rekening neemt. Een dergelijke klasse noemt ook wel een event handler. Er zijn verschillende manieren hoe we zo'n event handler kunnen gebruiken, een daarvan is via een inwendige klasse. Dit is een klasse defenitie binnen een bestaande klasse.
- Gebruik maken van gebeurtenisafhandeling gaat volgens 4 stappen
- Importeren van de nodige bibliotheken
- Maken van een event handler als een inwendige klasse
- Instantiëren van de event handler in de GUI
- Koppelen van het event-handler-object aan de component (knop, tekstvak,...)
Importeren van de nodige bibliotheken
- Om de gebeurtenissen op te vangen en af te handelen, hebben we bibliotheken nodig die voor ons die afhandeling doen. Deze bibliotheken zitten in de bibliotheek
java.awt.event.*
.
import java.awt.event.*;
Maken van een event handler als een inwendige klasse
- De inwendige klasse is een klasse die de afhandeling zal doen van de gebeurtenis. In Java bibliotheek is er een klasse die "luistert" naar gebeurtenissen en als er een gebeurtenis wordt afgevuurd, dan moet er iets gebeuren. De klasse heet
ActionListener
. De makers van Java weten natuurlijk niet wat er precies moet gebeuren in je applicatie, dus hebben ze een methode in de klasseActionListener
gemaakt die nog niet ingevuld is. De klasseActionListener
heeft zogenaamde interfaces die je als programmeur zelf een invulling kan geven. De in te vullen methode noemtactionPerformed
. Deze method handelt de gebeurtenis af en in de body geeft je aan wat er moet gebeuren bij het klikken op de knop. - De inwendige definitie van de klasse is dan:
class KnopHandler implements ActionListener{
public void actionPerformed(ActionEvent e){
//Vanaf hier geef je zelf de invulling van de methode actionPerformed
label.setText("Bedankt!");
}
}
Instantiëren van de event handler in de GUI
- Wanneer we de klasse gemaakt hebben, moeten we natuurlijk ook een object van de klasse maken. Het is immers het object dat de afhandelingstaak zal uitvoeren naar wat er in de klasse beschreven staat.
- In de creatie van de GUI kunnen we een instantie van de event handler maken op de tradionele manier:
KnopHandler kh = new KnopHandler();
Koppelen van het event-handlerobject aan de component
- Als het object gemaakt is, moeten we aan de component (knop of tekstvak) duidelijk maken dat hij zijn gebeurtenissen moet doorgeven aan de afhandelaar. Dit gebeurt door het volgende:
knop.addActionListener(kh);
- Je kan de vorige stap en deze stap combineren door gebruik te maken van een naamloze instantie van de klasse en deze door te geven aan de knop. Een naamloze instantie heet ook wel een anoniem object.
knop.addActionListener(new KnopHandler());
- Hieronder vind je een voorbeeld van de volledige code:
Paneel als een aparte klasse
- De voorgaande voorbeelden zijn relatief eenvoudig en beschouwen slechts één frame. In de praktijk behandelen we ingewikkeldere applicaties. Het is dan ook nuttig om een paneel te beschouwen als een aparte klasse. Een paneel is dan een subklasse van de klasse
JPanel
. Toegepast op het vorige kunnen we onze code scheiden in een klasse die verantwoordelijk is voor het maken van de GUI en een klasse die de applicatie aanroept.
WelkomstPaneel.java
WelkomstApp.java
- Het leuke is dat je via de klasse een abstractie gemaakt hebt van het paneel. Binnen code
WelkomstApp.java
heb je niet gezegd dat het paneel uit een knop en een label bestaat. De WelkomstApp hoeft dit ook niet te weten. Ook de afhandeling wordt volledige geabstraheerd door de klasseWelkomstPaneel
.
De klasse JTextField
- Via de klasse
JTextField
kan je een tekstvakje in je GUI steken waarin je tekst kan intypen. EenJTextField
-object maakt van alles wat je intypt een string. - De code die nodig is om een tekstvak te maken zijn:
private JTextField tekstvak;
tekstvak = new JTextField(20);
tekstvak.setText("Dit is een tekstvak");
paneel.add(tekstvak);
- Zo kan je een tekstvak bijvoegen in je applicatie. Stel dat we een applicatie willen maken met 2 knoppen en een tekstvak. De bedoeling is dat de ene knop zal de inhoud veranderen naar "Bedankt!" terwijl de andere knop zal de inhoud leeg ("") maken.
- Het gebruik van twee inwendige klassen is een mogelijkheid, maar het kan ook efficienter door slechts één handler te gebruiken. Het argument
ActionEvent e
van de methodeactionPerformed
kan aangeroepen worden om informatie te vragen over de bron van de gebeurtenis. Met de methodegetSource()
kun je opvragen wat de bron van het event is. - De event handler bestaat uit één klasse en ziet er dan als volgt uit: