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. 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:
  1. 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.
  2. Skriv den kod som ska utföras i den metod i interfacet som kommer att anropas när händelsen inträffar
  3. 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:

  1. Ett avbrott genereras enligt välkänt mönster från kursen Digital- och datorteknik
  2. Avbrottsrutinen drar igång och skickar via okända vägar information om mus/tangentbordshändelsen till Javamotorn.
  3. 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.
  4. 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.
  5. 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:

MouseEvent

Skickas av java.awt.Component och klasser som ärver därav. Lyssnare ska implementer gränssnittet MouseListener vilket innehåller följande metoder: MouseEvent skickas även av Component till registrerade MouseMotionListener vilka har följande metoder:

MouseWheelEvent

Skickas av java.awt.Component och klasser som ärver därav. Lyssnare ska implementer gränssnittet MouseWheelListener vilket innehåller följande metod:

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: 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:
  1. 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):
  2. 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.
     
  3. 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

  4. 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.