Ritprocedur och trådaspekter
Det finns några artiklar hos Sun som behandlar detta område.
Här kan du läsa om ritprocedurer http://java.sun.com/products/jfc/tsc/articles/painting/.
Här är tre artiklar om trådar:
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
http://java.sun.com/products/jfc/tsc/articles/threads/threads2.html
http://java.sun.com/products/jfc/tsc/articles/threads/threads3.html
Trådar och Swing
Det är absolut nödvändigt att följa riktlinjerna
i detta avsnitt, annars riskerar man att användargränssnittet
blir långsamt och beter sig konstigt.
Swing klarar inte mer än en tråd
Swing är, av prestandamotiv, inte skrivet för flertrådade
system.
Efter att någon av metoderna pack(), show()
eller setVisible(true) anropats får ingen annan tråd
än "event-dispatch thread" påverka utseendet hos en komponent
eller något av dess barn.
Vad är event-dispatch thread? Det är den tråd som skickar
till exempel mus- och tangentbordshändelser samt hanterar omritningar
begärda med repaint() och revalidate(). Alla dessa
begärda arbeten ligger köade i "event queue" och utförs i
tur och ordning av ovan nämnda event-dispatch thread.
Det finns några undantag till regeln ovan, det viktigaste är
att metoderna repaint(), revalidate() och invalidate() kan anropas
från vilken tråd som helst.
Hur lägga något i händelsekön?
Oftast behöver vi inte uppdatera en komponent på något
annat sätt än med ovan nämnda godkända metoder. Problem
uppstår dock om vi till exempel vill uppdatera en komponents utseende
på grund av något annat än en mus- eller tangenttryckning
eller om vi vill utföra någon uppgift som tar så lång
tid att det är olämpligt att blockera event-dispatch thread med
den.
Ett jobb kan placeras i event queue med de statiska metoderna SwingUtilities.invokeLater()
och SwingUtilities.invokeAndWait(). Båda tar ett objekt
av Runnable som parameter och lägger objektets run()
i event queue. Därifrån kommer den sedan att hämtas och köras
av event-dispath thread, alltså inte i en ny tråd. Runnable används
bara för att få en run()-metod, inte för att skapa
en tråd. InvokeLater() returnerar omedelbart och vi har ingen
aning om när koden verkligen körs, invokeAndWait() returnerar
när koden körts.
Om en tidsödande uppgift ska utföras som respons på en
inmatning från användaren måste vi dra igång en ny
tråd som utför uppgiften eftersom vi annars skulle blockera event-dispatch
thread och därmed hela grafikhanteringen. Här är ett exempel
på ett program som gör just det LongOperation.java.
Ritproceduren
I mycket korta drag ritas en komponent på följande vis.
Fall 1 Ritandet börjar med att en tungviktskomponent (till exempel
JFrame, JDialog eller JApplet) ska ritas om.
- Event-dispatch thread anropar paint() på tungviktaren.
- Tungviktarens paint(), dvs Container.paint(),
anropar paint() på alla sina barn.
- Om barnet är en JComponent utförs följande
av JComponent.paint()
- Anropa paintComponent(). Den ritar om aktuell komponent
genom att anropa update() i komponentens UI-delegate, vilken ritar
komponentens bakgrund (om den inte är genomskinlig) och därefter
i sin tur anropar paint() i UI-delegate som slutligen ritar
komponenten.
- Anropa paintBorder() för att rita komponentens kanter.
- Anropa paintChildren(), vilken anropar paint()
i alla komponentens barn som behöver ritas om.
Fall 2 Ritandet börjar med att repaint() anropas på
en JComponent.
- JComponent.repaint() lägger in en begäran i RepaintManagers
kö för omritningar. RepaintManager kommer i sinom tid
att skicka begäran till event queue.
- När begäran i sinom tid exekveras av event-dispatch thread
anropas paintImmediately() i komponenten, vilken utför följande:
- Med hjälp av den yta som angavs för omritning vid anropet
av repaint() och egenskaperna opaque och optimizedDrawingEnabled
avgörs vid vilken av aktuell komponents förfäder omritningen
måste börja.
- Anropa paint() i den komponent där omritningen ska
börja, varvid paintComponent(), paintBorder() och
paintChildren() anropas enligt ovan.
Regler för ritning
För att inte skärmen ska se konstig ut måste ritandet
ske enligt vissa regler, här är de viktigaste. Om ditt program
beter sig konstigt kan det vara bra att kolla att du uppfyller dessa regler.
- Metoden update() i JComponent, som härstammar
från AWT, används aldrig.
- Omritning ska alltid startas med anrop av repaint(), aldrig
paint().
- Om doubleBuffered, opaque eller optimizedDrawingEnabled
är falska kan det ta väsentligt längre tid att rita komponenten.
- När grafik ska ritas i en komponent ska paintComponent()
omdefinieras, inte paint().
- Om paintComponent() omdefinieras måste super.paintComponent()
anropas för att UI-delegate ska rita själva komponenten.
Här följer två exempel på grafik. Det är primitiva
tillämpningar av drag-and-drop och rubberbanding.
I det här exemplet kan du klicka i rektangeln och flytta den, MoveGraphics.java. Här kan du klicka och
dra med musen nedtryckt, ett streck ritas då från den punkt du
tryckte ned musen till aktuell position, RubberBand.java.
Eftersom vi inte ännu avhandlat de inblandade fönstren kanske
det kan vara idé att återvända till dessa exempel senare.
Valideringsproceduren
JComponent innehåller metoderna invalidate(), validate()
och revalidate(), vilka hanterar omritning av en komponent tillföljd
av att dess layout behöver uppdateras.
En komponent har en property valid. Är den false
är dess layout ogiltig och den behöver ritas om. Klassen RepaintManager
har en kö med komponenter som har ogiltig layout. Den placerar en i
taget av dessa på event queue 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, varvid validate() anropas och layouten återigen
blir giltig.