Repetition av AWT
Eftersom projektet ska utföras med Swing och AWT dessutom redan
är bekant är detta långt ifrån en fullständig
genomgång. Endast sådant som är gemensamt för
AWT och Swing tas upp. Observera att Swings klasser ärver från
klasserna i AWT, varför Swing inte ersätter AWT utan utvecklar
det. Allt på denna sida är skrivet för AWT, men ytterst
små förändringar krävs för att göra om
exemplen till Swing.
Applikationer och applets
Ett grafiskt användargränssnitt i java är antingen
en applikation eller en applet. Nedanstående exempel visar vad
som krävs för att något över huvud taget ska visas
på skärmen.
Applet
En applet visas i en webbläsare med hjälp av taggen
<applet>. En applet ska ärva av klassen java.applet.Applet.
I den kan du omdefinera nedanstående metoder.
- public void init() Anropas endast när appleten
laddas in i webläsaren, dvs en gång per applet.
- public void start() Anropas när websidan
med appleten ska börja visas, dvs när användaren surfat
till den eller när programmet varit ikoniserat och börjar visas
igen.
- public void stop() Anropas när websidan
med appleten slutar visas, dvs när användaren surfar från
den eller ikoniserar webläsaren.
- public void destroy() Anropas när appleten
laddas ur, till exempel om användaren stänger webläsaren.
Här är en enkel liten applet och en HTML-fil som startar
den, AppletDemo.java, appletdemo.html. Om du startar appleten med
appletviewern (skriv "appletviewer appletdemo.html" i ett DOS-fönster)
så skriver den ut när ovanstående fyra metoder anropas.
Applikation
Om programmet ska ha ett grafiskt användargränssnitt
utan att visas i en webbläsare ska det ärva av java.awt.Frame.
Det måste innehålla ett anrop av metoden setVisible( true
); för att framen ska synas på skärmen. Om du vill
ge framen en viss storlek anropar du dessutom metoden setSize(),
vill du både ge den en storlek och ett läge på skärmen
använder du setBounds(). Istället för setSize()
kan metoden pack() anropas. Den gör framen så stor
att alla dess ingående Component precis får plats.
När användaren stänger framen, till exempel genom att klicka
på krysset i övre högra hörnet, anropas metoden
windowClosing i registrerade WindowListener. Det är
klokt att registrera en sådan lyssnare och stänga framen (eventuellt
även avsluta programmet) när windowClosing anropas.
Det kan göras med följande kodsnutt vilken använder sig
av en anonym inre klass som händelsehanterare.
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
Här kommer ett exempel på en mycket enkel applikation, ApplikDemo.java.
Händelsehantering
För att en viss kod ska exekveras till följd av händelsen
XXX utför du följande:
- Skriv en klass som implementerar interfacet XXXListener.
Vill du inte implementera alla metoder i interfacet kan du ärva
adapterklassen XXXAdapter i stället. Den har tomma
implementationer av alla interfacets metoder, du behöver bara omdefiniera
de du vill ska göra något.
- Skriv den kod som ska utföras i den metod i interfacet
som kommer att anropas när händelsen inträffar
- Registrera interfacet som lyssnare genom att skicka det
till metoden addXXXListener() i den klass som det ska lyssna
på.
Ett bra exempel på ovanstående förfarande är
hur programmet ApplikDemo.java ovan lyssnar
efter WindowEvent.
Egentligen är det ingen som lyssnar...
Det är viktigt att förstå att när en lyssnare
registreras, se ovan, dras det inte igång något trådmaskineri
som ligger och letar efter händelser. Vad som inträffar när
en metod addXXXListener() anropas är uteslutande att
den anropade klassen sparar referensen till lyssnaren i en vektor. När
det sedan är dags att "skicka händelsen" gör den avlyssnade
klassen ett helt vanligt metodanrop till alla XXXListener den
har i vektorn.
När användaren trycker på en tangent eller gör något
med musen inträffar följande:
- Ett avbrott genereras enligt välkänt mönster
från kursen Digital- och datorteknik
- Avbrottsrutinen drar igång och skickar via okända
vägar information om mus/tangentbordshändelsen till Javamotorn.
- Javamotorn lagrar informationen om händelsen på
sin händelsekö, EventQueue. Därefter är
avbrottshanteringen färdig och vårt Javaprogram fortsätter
exekveringen där det blev avbrutet.
- I sinom tid får Swings händelsehanteringstråd,
EventDispatchThread, köra. Den kommer då att
hämta händelseinformationen från EventQueue
och skicka den till berörd klass, till exempel den frame där
användaren klickade på krysset i exemplet ovan.
- När "Swingbetydelsen" (till exempel "användaren
har klickat på krysset i en frame" eller "användaren har
klickat på en knapp") är utredd gör den avlyssnade klassen
till slut det helt vanliga metodanropet till de registrerade lyssnarna.
I fallet "användaren har klickat på krysset" blir det metoden
windowClosing() som anropas. Koden i den kommer alltså
att exekveras av EventDispatchThread.
Ovan nämnda EventDispatchThread har hand om all
uppdatering av skärmen, även till följd av att användaren
till exempel ändrar storlek på ett fönster eller fäller
ut en meny. Det innebär att hela användargränssnittet
är fullkomligt låst tills EventDispatchThread avslutat
exekveringen av händelsen. Därför är det viktigt att
koden i metoder som anropas av EventDispatchThread inte tar
för lång tid att exekvera. Se vidare avsnittet om ritproceduren och trådaspekter.
Några viktiga händelser
Här kommer en kort förteckning över en liten
del av de allra vanligaste händelserna. Det är inte alltid
de kastas när man tror, det kan vara bra att göra ett litet
program som skriver ut något i händelsehanteringsmetoderna
och kolla när de egentligen anropas.
WindowEvent
Skickas av java.awt.Window och klasser som ärver
därav. Lyssnare ska implementera gränssnittet WindowListener
vilket innehåller följande metoder:
- windowActivated() Anropas när fönstret
blir aktivt, dvs blir det fönster som tar hand om användarens
tangentbordsinmatning.
- windowDeactivated() Anropas när fönstret
inte slutar vara aktivt.
- windowClosing() Anropas när användaren
försöker stänga fönstret.
- windowClosed() Anropas när fönstret
har stängts, dvs när dess metod dispose() anropats.
- windowIconified() Anropas när användaren
ikoniserar fönstret.
- windowDeiconified() Anropas när fönstret
varit en ikon och användaren öppnar det igen.
- windowOpened() Anropas första gången
fönstret blir synligt på skärmen.
MouseEvent
Skickas av java.awt.Component och klasser som ärver
därav. Lyssnare ska implementer gränssnittet MouseListener
vilket innehåller följande metoder:
- mouseClicked() Anropas när användaren
klickat med musen, dvs tryckt ner och släppt upp på exekt
samma pixel.
- mouseExited() Anropas när cursorn kommer
in över en Component
- mouseEntered() Anropas när cursorn lämnar
en Component
- mousePressed() Anropas när användaren
trycker ned en musknapp.
- mouseReleased() Anropas när användaren
slåpper upp en musknapp.
MouseEvent skickas även av Component
till registrerade MouseMotionListener vilka har följande
metoder:
- mouseDragged() Anropas när användaren
flyttar musen med en knapp nedtryckt.
- mouseMoved() Anropas när användaren
flyttar musen utan någon knapp nedtryckt.
MouseWheelEvent
Skickas av java.awt.Component och klasser som ärver
därav. Lyssnare ska implementer gränssnittet MouseWheelListener
vilket innehåller följande metod:
- mouseWheelMoved(MouseWheelEvent e) Anropas
när användaren snurrar på musens hjul.
ActionEvent
Skickas till exempel av java.awt.Button och av javax.swing.JButton
när användaren klickat på knappen samt av java.awt.TextField
och javax.swing.JTextField när användaren trycker
på retur i textfältet. Metoden actionPerformed()
anropas då i registrerade ActionListener.
KeyEvent
Ska inte behöva användas i Swing.
Layouthantering
Swings basklass, javax.swing.JComponent, ärver
av java.awt.Container (vilken i sin tur ärver av java.awt.Component).
En Container har en LayoutManager, vilken anges med
metoden setLayout(). Container delegerar till sin
LayoutManager uppgiften att beräkna storlek och position
för sig själv och alla sina ingående JComponent/Component.
En JComponent har properties som anger minimumSize, maximumSize
och preferredSize. Det är med hjälp av dessa som en LayoutManager
beräknar storlek. Det är dock helt OK för en LayoutManager
att ignorera dessa properties.
I AWT finns nedanstående implementationer av interfacet LayoutManager.
FlowLayout
Resekterar preferredSize. Placerar Componenter
från vänster till höger, uppifrån och ner i den
ordning de läggs till (med add()). Här är ett
exempel, FlowEx.java.
BorderLayout
Placerar Componenter i ett av dessa fem olika fält:
- BorderLayout.NORTH Överst i fönstret,
preferredHeight respekteras men bredden blir hela fönstrets
bredd.
- BorderLayout.SOUTH Nederst i fönstret, preferredHeight
respekteras men bredden blir hela fönstrets bredd.
- BorderLayout.EAST Längst till höger
i fönstret, preferredWidth respekteras men höjden
blir hela fönstrets höjd.
- BorderLayout.WEST Längst till vänster
i fönstret, preferredWidth respekteras men höjden
blir hela fönstrets höjd.
- BorderLayout.CENTER Mitt i fönstret. Fyller
upp allt ledigt utrymme.
Här är ett exempel, BorderEx.java.
GridLayout
Delar in sin Container i en matris med angivet antal
rader och kolumner. Varje utplacerad Component fyller en cell
i matrisen, preferredSize ignoreras alltså. De ingående
Componenterna placeras från vänster till höger,
uppifrån och ned i den ordning de läggs till (med add()).
Här är ett exempel, GridEx.java.
Kombinationer av flera LayoutManager
Genom att kombinera ovanstående tre olika LayoutManager
på olika sätt klarar man kanske 90% av fallen. Det underlättar
väsentligt om man nöjer sig med att layouten är bra och
lättförstådd istället för att försöka
få den att vara exakt på ett visst förutbestämt sätt.
Här är ett exempel där olika LayoutManager kombineras,
CombinedEx.java.
GridBagLayout
En mycket kraftfull LayoutManagar som alltid fungerar.
Nackdelen är att den är lite krånglig. Det finns två
olika sätt att använda LayoutManagers, det ena är
att använda kombinationer enligt ovanstående stycke och det
andra är att alltid använda GridBagLayout. Det finns
egentligen aldrig någon anledning att använda både GridBagLayout
och någon annan LayoutManager.
GridBagLayout delar in sin Container i celler,
precis som GridLayout, men till skillnad från denna kan
Componenter i GridBagLayout täcka flera celler.
De behöver dessutom inte fylla ut hela cellen. Det finns ett stort
antal egenskaper som specificeras för varje Component och
som anger exakt vilka delar av vilka celler den Componenten
ska täcka och vad som ska hända med Componenten om
Containern ändrar storlek. Dessa egenskaper finns i klassen
GridBagConstraints. En genomgång av dessa egenskaper finns
här, http://java.sun.com/docs/books/tutorial/uiswing/layout/gridbagConstraints.html.
Här kommer en metod att bestämma värdet av dessa
egenskaper:
- Rita en bild av hur du vill att fönstret ska se ut.
Låt oss anta att vi vill konstruera ett sådant här fönster
(När layouten ska konstrueras är det bäst att rita fönstret
på ett papper):
- Dela in fönstret i rader och kolumner. Det gör
inget om Componenter fyller mer än en cell, däremot
ska det inte finnas mer än en Component i samma cell.
- Gör en tabell där egenskaper kan anges för
de olika Componentern
Component
|
gridx
|
gridy
|
gridwidth
|
gridheight
|
fill
|
anchor
|
weightx
|
weighty
|
insets (top, left, bottom, right)
|
Add
|
0
|
0
|
1
|
1
|
NONE
|
CENTER
|
0
|
0
|
2, 2, 2, 0
|
Remove
|
1
|
0
|
1
|
1
|
NONE
|
CENTER
|
0
|
0
|
2, 2, 2, 0
|
Clear
|
2
|
0
|
1
|
1
|
NONE
|
WEST
|
1
|
0
|
2, 2, 2, 0
|
textarea tv
|
0
|
1
|
3
|
1
|
BOTH
|
CENTER
|
1
|
1
|
0, 0, 0, 0
|
textarea th
|
3
|
1
|
1
|
1
|
BOTH
|
CENTER
|
1
|
1
|
0, 0, 0, 0
|
- Skriv in samma egenskaper i programmet som i
tabellen. Här kommer det färdiga programmet, GridBagEx.java.
Ingen LayoutManager alls
Det är fullt möjligt att avstå från att
använda LayoutManagers över huvud taget. Det gör
du med metoden setLayout( null ). Om du gör det blir
du sedan tvungen att ange alla ingående Componenters position
och storlek med metoderna setSize(), setBounds() och
setLocation(). Detta rekomenderas inte!!
Kanhända får du det att se väldigt bra ut på din
skärm just vid designtillfället, men vad händer om användaren
har en annan storlek på skärmen eller ändrar fönstrets
storlek? Förmodligen kommer det att se mycket konstigt ut. Du går
dessutom miste om LayoutManagerns hjälp att beräkna
preferredSize på din Container.
När och hur ritas en JComponent om?
JComponent innehåller metoderna invalidate(),
validate() och revalidate(), vilka hanterar omritning
av en komponent tillföljd av att dess layout behöver uppdateras.
En JComponent har en property valid. Är den false
är dess layout ogiltig och den behöver ritas om. Klassen RepaintManager
har en kö med JComponent som har ogiltig layout. Den
placerar en i taget av dessa på EventQueue för
omritning.
Vill du tvinga en komponent att rita om sig ska du anropa revalidate().
Den kommer först att anropa invalidate() på komponenten
och dess förfäder som ligger i samma Container som
den på vilken du anropade revalidate(), och därmed
sätta valid till false och göra layouten ogiltig.
Därefter placeras hela Containern i kön för ogiltiga
layouter och i sinom tid blir den omritad (av EventDispathThread),
varvid validate() anropas och layouten återigen blir giltig.
I Swing är det ganska sällan revalidate() behöver
anropas eftersom den ofta anropas automatiskt om någon property
som påverkar en JComponents utseende förändras.
Här är ett exempel där revalidate() anropas för
att tvinga knappen att ritas om när dess font har ändrats,
RevalidateExample.java. Det är
det dock onödigt eftersom anropet görs i metoden setFont().
Om du kommenterar bort revalidate() visar det sig att knappen
ritas om i alla fall.
Grafik, färg och fonter
Swing använder (precis som AWT) klassen java.awt.Graphics
för grafik. Även vad gäller färg och fonter använder
Swing AWT:s klasser.
Observera dock att bildskärmshanteringen inte sköts
på exakt samma sätt i AWT som i Swing varför det inte
går att använda ett
Graphics-objekt på samma sätt vid samma tillfälle
i de båda. Se vidare avsnittet om ritproceduren.
Här kommer en mycket kortfattad redogörelse för enkel hantering
av grafik, färg och fonter, bildhantering tar vi upp senare.
Avancerad 2D-grafik hanteras i API:t
Java 2D. Avancerad 3D-grafik (och den är verkligen avancerad)
hanteras i Java 3D. Det ingår inte i kursen men du kan läsa
om det på http://java.sun.com/products/java-media/3D/.
Grafik
Varje Component har ett Graphics-objekt associerat
till sig. När något ritas i ett Graphics-objekt
syns det på skärmen i den Component som ägde objektet.
Med metoderna drawXXX() och fillXXX() ritas olika
geometriska former. Med metoden drawString() skrivs text. setColor()
och setFont() anger vilken färg och font ett Graphics-objekts
innehåll har.
Färg
Färger hanteras av klassen java.awt.Color. Den
innehåller en mängd konstanter som representerar olika färger
och har även konstruktorer med vilka man kan skapa färger på
olika sätt. Ett vanligt sätt att definiera en färg är
som RGB, vilket innebär att färgen anges som en blandning av
rött, grönt och blått av olika styrka. En Components
färg sätts med metoderna setForeground() och setBackground().
Font
Fonter representeras av klassen java.awt.Font och en Components
font sätts med metoden setFont(). Väsentligt mer om fonter
finns att läsa under avsnittet om Java 2D.