OOP e2002 - uge 43 class Scribble (Tegnebrættet) Det simplest mulige tegnebræt kun en funktion: tegn streger med musen ingen kontrolknapper/valgmuligheder skal dog kunne "holde til" at ændre størrelse Frihåndstegning: Tegnebrættet har ikke objekter der repræsenterer de streger etc., der er tegnet canvas GUI designes som bestående alene af et canvas Det tegnede ligger kun repræsenteret som pixels i et Image-objekt. Modsat boldappletten, hvor boldene er repræsenteret med x/ykoordinater etc. (evt. som selvstændige objekter) Xiaoping implementerer iteration 2-3-4 ved tilføjelser, herunder subklasning. Bogen bliver lettere at læse Den samlede kode bliver sværere at læse, da den indeholder overflødige dele.
Det primære objekt er en grafik-"dobbelt"-buffer Skabelse af buffer Objekterne: grafik-bufferen og dens hjælper: class ScribbleCanvas extends JComponent { Graphics offscreengraphics; // Rummer platformsafh. data/metoder Image offscreenimage; // De rå data i platformsuafh. format Tegning i bufferen (når brugeren giver tegne-input) scribblecanvas.offscreengraphics.drawline(..); Visning af bufferen på skærmen: scribblecanvas.repaint();.. update(graphics g) { g.drawimage(offscreenimage,..); // bufferen vises via hjælperen setbounds(int x, int y, int width, int height) { // kaldes af kontekst når canvas bliver synlig offscreenimage = creageimage(width, height); offscreengraphics = createimage.getgraphics();.. Graphics/Image: For at skabe et ny Graphics-objekt, skal vi først skabe et Image-objekt Metoder til at tegne opererer på Graphics-objektet (f.eks. drawline), men opdaterer automatisk det Image-objekt, der er brugt til at skabe det! OOP e2002 - uge 43 Skabelse af canvas ved at nedarve fra (J)Component?? class ScribbleCanvas extends JComponent {.. eller uden : class ScribbleCanvas extends Component {..
OOP e2002 - uge 43 Hændelser der kan opfange brug af "pen" Brug af pen udløser: hændelse = brugerens tegne-bevægelser handling = programmet tegner på brættet Vores musehændelse: museknappen trykkes ned, musen bevæges mens knappen holdes nede, til sidst slippes knappen igen Alle former for musebevægelser, ikke kun lige streger. Men tegn kun streg hvis musen er inde over brættet når knappen trykkes ned (og pen er valgt) Hændelsesstyret programmering - hvilken hændelse? Fremgangsmåde ved implementation af penværktøj Hændelseskilde Hændelse Lytter Vi skal Hændelser repræsenteres af et hændelsesobjekt indeholder hændelsens kilde og helst mange flere oplysninger Handlinger repræsenteres af lyttere (listeners): klasser hvis metoder skal udføres Konteksten lytter til. For at konteksten kan aktivere en lytter i tilfælde af en hændelse, skal lytteren registreres hos hændelseskilden. 1. finde en eller flere passende (dvs. subklasse(r) af class AWTEvent) og 2. implementere værktøjet som en lytter (en klasse, der implementerer et/flere Listener-interfaces) der kan modtage ne.
Implementation af penværktøj (ideelt) Hændelses-klasse: class MouseDraggedEvent Lytter-interface: interface MouseDraggedListener { public void MouseDragged(MouseDraggedEvent e); hvor et MouseDraggedEvent-objekt indeholder alle punkter, musen har bevæget sig henover, mens museknappen har været holdt nede. Findes desværre ikke! class java.awt.event.mouseevent Kan repræsentere følgende simple og sammensatte muse: pressed: museknap trykket released:.. sluppet clicked: trykket + sluppet entered: musen er kommet ind over komponenten exited:.. væk.. moved: mus bevæget dragged: mus bevæget med knap holdt nede Indeholdt information: hvilke(n) af ovenstående er indtryffet 1 sæt koordinater (x,y): her foregik (sluttede) hændelsen Opgave: hvordan kan disse kombineres til den hændelse, vi har defineret skal repræsentere en streg? OBS: Hændelser genereres med visse mellemrum (brøkdele af et sekund) Løsning: mouse pressed + dragged + dragged.. mouse pressed ("her") mouse dragged ("hertil") linjestykker der tegnes på tegnebrættet musens virkelige bevægelser Lyttere, som kan modtage et java.awt.event.mouseevent Der findes to "musehændelses-lyttere". De modtager hver sin type af muse-: De "statiske" : interface MouseListener{ mousepressed(mouseevent e);.. De "dynamiske" : interface MouseMotionListener{ mousedragged(mouseevent e);..
Pen implementeret som lytter (frit efter XP s323 f) class PenTool implements MouseListener, MouseMotionListener { Point mouse; // musens "gamle" position mousepressed(mouseevent e) { mouse = e.getpoint(); mousedragged(mouseevent e) { Point p = e.getpoint(); tegn_streg_fra_mus_til_p(); mouse = p; canvas.repaint();.. plus tomme definitioner af resten af lytter-metoderne Ikke nødvendigt at "spørge" et MouseEvent-objekt hvilken slags hændelse, det repræsenterer, da dette fremgår af den kaldte lytter-metode. Men nødvendigt at registrere PenTool-objekt som Mouse- og MouseMotion-lytter OOP e2002 - uge 43 Grafik-opdatering: modificer så lidt som muligt repaint() --> update(), paint() Når konsteksten kalder update() efter applikations-genereret repaint(): mousedragged(mouseevent e) { Point p = e.getpoint(); tegn_streg_fra_mus_til_p(); mouse = p; canvas.repaint(<kun den del der er blevet ændret>); CPU-kraften må ikke spildes til at gentage den del af kanvas, der ikke har ændret sig. Ellers bliver reaktionstiden på musebevægelser langsommere. repaint(x, y, width, height) kan bruges uden man behøver lave en særlig definition af update()/paint() 0. Muligvis ventes der lidt først. 1. Graphics g = det nuværende billede (det sidst tegnede); 2. kald af update(g); // altid hele g! (svarende til components størr.) 3. tegn(g); // repaint(<rect>) => tegn(g,<rect>) Container superklasse opfanger kald af update() hvis ikke overskrivet i subklasser: public void update(grahpics g) { g.setcolor(getbackground()); // vælger baggrundsfarven g.clearrect(0, 0, width, height); // udfylder g med farven g.setcolor(getforeground()); // vælger forgrundsfarven paint(g); public void paint(graphics g) { {stort set tom definition {man kan kalde paint() i alle subkomponenter
OOP e2002 - uge 43 Skabelse/ af buffer (XP s 232) Større canvas => nødvendigt med større buffer til at repræsentere tegningerne. Vi vil gerne overføre eksisterende tegning til ny større canvas Der findes ikke en metode til at lægge pixels til i yderområdet. Løsning: overskrivning af setbounds() (XP s. 323) setbounds(int x, int y, int width, int height) { // kaldes også når kanvas ændrer størrelse (system- eller appl.-udløst).. Image resizedimage = creageimage(width, height); // midlertidigt Image offscreengraphics = resizedimage.getgraphics(); clearcanvas(); if (offscreenimage!= null) // overfør "gamle" tegning til buffer offscreengraphics.drawimage(offscreenimage,..); offscreenimage = resizedimage; // Image-objektet opdateres super.setbounds(..); // repaint(); Graphics/Image: den gamle tegning af lagret i offscreenimage den "resizede" tegning er lagret i newimage derfra overføres den til sidst til offscreenimage OOP e2002 - uge 43
Kombineret En applet kan køres som applikation, hvis appletten udvides til at definere en main()-metode main() opretter et yderste vindue som applettens panel kan anbringes i init() og andre metoder, der kaldes automatisk af appletkonteksten, skal kaldes eksplicit, hvis de er re-defineret i appletten. public class Scribble extends JApplet { init();... start();.. stop();.. ; public static void main(string[] args) { (a) skab yderste vindue med passende titel (b) skab Scribble-objekt (c) anbring Scribble-objekt (et panel) i vinduet To Scribble-konstruktorer: Applet: konteksten kalder Scribble() Applikation: vi kalder Scribble(..) som eksplicit kalder init() m.m. main() i applet public static void main(string[] args) { // (a) Det yderste vindue JFrame frame = new JFrame(); frame.settitle("scribble Pad"); frame.addwindowlistener(..); // registrer en lytter til at reagere på "sluk" // (b) Skabelse af nyt Scribble-panel i vinduet Scribble scribble = new Scribble(false); // (c) Anbringelse af Scribble-panel i vinduet frame.getcontentpane().setlayout(new BorderLayout()); // JComponent.getContentPane() // giver reference til JRootPane-objekt // som kan bruges til "Container-opgaver" frame.getcontentpane().add(scribble, BorderLayout.CENTER); // plus div. detaljer frame.setsize(..);..; OOP e2002 - uge 43 java.awt contra javax. javax. indeholder JButton, JFrame,.. med funktion svarende til Button, Frame,.. Features mange med nye features, f.eks. ikoner og ledetekst i knapper. man kan vælge at bruge indbygget dobbelt-bufring hurtigere? pænere? nye typer grafik-komponenter bl.a. JScrollBar nye typer events (og events-lyttere) Implementation "Letvægtskomponenter" forstået som at de er alene repræsenteret internt i Java og har lavniveaugrænseflade til platform modsat "sværvægtskomponenter" som har en "peer" komponent implementeret vhja. platformens højniveau-grafik-faciliteter ikke en erstatning af java.awt men en udvidelse JButton m.fl. nedarver fra java.awt.component overgang til at bruge javax. kan ske skridtvis
OOP e2002 - uge 43 WEST : Specifikation af krav til GUI: Bjælker med knapper ude langs siden af selve brættet Et bjælke til valg af "værktøj" (pen, viskelæder,..) Et bjælke til generelle/tværgående valg (farve, slet alt,..) SYD CENTER BorderLayout bruges til inddeling af panel CENTER et canvas til brættet WEST container til knapper SYD container til knapper Håndtering af class EraserTool implements MouseListener,.. { // minder om PenTool // skriver små hvide rektangler i stedet for streger Det primære problem er at skifte mellem forskellige værktøjer. 1) Man kan af-registrere den gamle lytter og registrere den nye. (Besværligt). 2) Man kan registrere en super-lytter, som fanger alle, og giver den videre til det aktuelle værktøj. Nemt løbende at ændre ToolListener-en. Bruger "State" design pattern. Kilde: canvassen Muse- Hændelse ToolListener (XP p 338) Muse- Hændelse Værktøj 1 Værktøj 2 OOP e2002 - uge 43
Xiaopings BouncingBall3 (s. 282f) (sidste iteration!) Animerings- og dobbeltbufrings-funktionalitet genbruges fra class Animator og class DoubleBufferHandler Der er tale om genbrug af funktionalitet ved delegering ikke som tidligere ved nedarvning (f.eks. BouncingBall2 s. 193) Før: nedarvning AnimationApplet tager sig af alt vedr. animering (dog ikke dobbelt-bufring) Oprettelse af en separat tråd, kald af sleep()/repaint() fra trådens run(), m.m. Subklasser skal blot definere paint() samt evt. justere delay. DBAnimationApplet har sin egen final update(), der kalder paintframe(), der er en abstrakt metode. Nu: delegering Fordelen delegering af animeringsfunktionalitet Generelt er fordelen ved delegering fremfor nedarvning: bruger-klassen kan nedarve fra andre klasser I tilfældet med Applet-animering er dette ikke relevant, da AnimationApplet i forvejen nedarver fra den klasse, vi vil nedarve fra (class Applet) Her drejer fordelen sig om: I BouncingBall ønsker vi ikke at animere/dobbeltbufre hele Appletten/panelet, kun canvas-et med boldene Hvilke fordele kan der være herved? Det ville være muligt at lave en class AnimationCanvas, der nedarver fra class Canvas, men løsningen med class Animator er mere generel
FinalBouncingBall med delegering af animering + dobbeltbufring (XP s. 282) DBHandler (XP s. 280) class DBHandler { DBHandler (DBComponent comp) { this.comp = comp; DBComponent comp; void update(graphics g) { comp.paintframe(offscreengraphics); {overfør offscreengraphics til g; Graphics offscreengraphics; BouncingBallCanvas (XP s. 281) class BouncingBallCanvas { void update(graphics g) { dbhandler.update(g); void paint(graphics g) { update(g); // hvorfor? DBHandler dbhandler; update() vs. paint(): hvilke(n) skal overskrives i Applet-subklasser? Hvis subklasser overskriver update(): skal de selv sørge for at gentegne hele baggrunden men så slipper de for at vente på at appletkonteksten gentegner baggrunden. Hvis man bruger dobbelt-bufring: bør man overskrive update() fordi man alligevel selv skal tegne baggrunden (da man jo ikke tegner i det grafikobjekt der er skabt i konteksten, men tværtimod overskriver det med ens egen "buffer") Definitionen af paint() skal fange "usædvanlige" kald af paint() fra konteksten, der sker uden om update().
OOP e2002 - uge 43 Anonyme klasser Bruges ofte til definition af lytter-klasser, hvor man kun skal bruge en instans, og ikke skal referere til instansen. En anonym klasse er en form for indre klasse. En indre klasse er en klasse defineret i en anden klasses indre. Definitionen af en anonym klasse er et udtryk, hvis værdi er en instans af den anonyme klasse. I stedet for - class StartStopHandler implements ActionListener {.. ActionListener handler = new StartStopHandler(); addactionlistener(handler); kan man skrive - addactionlistener(new ActionListener() {..);