Skansholm: Kapitel 6-8
Här tittar vi på ett litet ritprogram. Vi startar med ett mycket enkelt program som vi bygger ut efterhand.
Enklast möjliga ritprogram (program/swing-2/Punkter_0.java):
public class Punkter_0 extends JPanel implements MouseListener{ public static void main (String[] arg) { JPanel s = new Punkter_0(); JFrame f = new JFrame(); f.add(s); f.setVisible(true); f.setBounds(100, 100, 400, 400); f.setVisible(true); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public Punkter_0 () { setBackground( Color.YELLOW ); setForeground( Color.BLACK ); addMouseListener(this); } public void mouseClicked (MouseEvent e) { int x = e.getX(); int y = e.getY(); Graphics g = getGraphics(); g.setColor(Color.black); g.fillOval(x, y, 10, 10); } public void mouseEntered (MouseEvent e) { } public void mouseExited (MouseEvent e) { } public void mousePressed (MouseEvent e) { } public void mouseReleased (MouseEvent e) { } }
(Jag kommer inte att säga nåt om koden som sätter upp fönstret, väljer färger etc. Allt detta fungerar ungefär som i tidigare exempel.)
Först, notera att Punkter_0
ärver JPanel
och implementerar
MouseListener
. Gränssnittet MouseListener
deklarerar fem metoder.
I det första programmet är det bara metoden mouseClicked()
som är
intressant. När användaren klickar på fönstret sker följande: Metoden
tar reda på var användaren klickade (x
och y
). Sedan plockar vi fram
det grafikobjekt som är associerat med nuvarande komponent. Vi väljer
svart färg, och ritar en cirkel med diameter 10 vid koordinaten x
och
y
.
Så här kan det se ut:
En irriterande detalj är att punkten inte hamnar där musen pekar, utan lite nedanför till höger. (Varför?)
Ett annat problem är att bilden försvinner om fönstret skyms. Allt som
ritas med Graphics
är temporärt. För att bilden ska finnas kvar måste
den lagras i någon datastruktur. (På moderna operativsystem kan bilden
sparas även när den skyms. Prova i så fall att minimera fönstret och
återställa det.)
För att göra det lite lättare att arbeta med programmet stuvar jag om
en smula. Jag introducerar en klass "Punkt
" (mera nedan) och klassen
Ritpanel
som även agerar muslyssnare.
Om vi vill representera en bild med en datastruktur måste vi först
bestämma hur varje enskild punkt ska representeras. Vi börjar med att
definiera en klass Punkt
.
class Punkt { private int x, y; public Punkt (int x0, int y0) { x = x0; y = y0; } public void draw(Graphics g) { g.setColor(Color.BLACK); g.fillOval(x, y, 10, 10); } }
En punkt har två instansvariabler, x
och y
(dess koordinater). Den har
en metod för att rita upp den.
Program program/swing-2/Punkter_1.java.
Program program/swing-2/Punkter_2.java.
I varje program där man ritar och manipulerar bilder måste man lagra informationen i bilden på nåt sätt.
Jag lägger en instansvariabel punktMängd
= till klassen
Ritpanel
. I den här applikationen utgör ju punkterna hela bilden.
Ritpanel
definierar en metod addPunkt()
.
När användaren klickar med musen anropas metoden addPunkt()
. Sen
anropas repaint()
. Jag valde att anropa repaint()
i muslyssnaren.
Det kanske vore bättre att lägga alla anrop till repaint()
i klassen
Ritpanel
, men ett mål med exmplet var ju att visa hur man kan styra
uppritning med repaint()
och då vore det väl fel att gömma undan
anropet.
Sen definierar vi också en metod paintComponent()
. Den anropas av
systemet, till exempel när fönstet varit skymt och måste ritas om.
class Ritpanel extends JPanel implements MouseListener { private Collection <Punkt> punktMängd = new ArrayList<Punkt>(); public Ritpanel () { setBackground( Color.YELLOW ); addMouseListener(this); } public void addPunkt (Punkt p) { punktMängd.add(p); } public void paintComponent (Graphics g) { super.paintComponent(g); for (Punkt p : punktMängd) { p.draw(g); } } public void mouseClicked (MouseEvent e) { Punkt p = new Punkt(e.getX(), e.getY()); addPunkt(p); repaint(); } public void mouseEntered (MouseEvent e) { } public void mouseExited (MouseEvent e) { } public void mousePressed (MouseEvent e) { } public void mouseReleased (MouseEvent e) { } }
Program: program/swing-2/Punkter_3.java
Låt oss lägga till kod för att redigera bilden. Vi börjar med det allra enklaste: gör så att man kan ta bort punkter.
Här är klassen Punkt
.
class Punkt { private int x, y; static final int radie = 5; public Punkt (int x0, int y0) { x = x0; y = y0; } public void draw(Graphics g) { g.setColor(Color.BLACK); g.fillOval(x-radie, y-radie, radie*2, radie*2); } public static int sqr(int x) { return x*x; } public boolean rymmer (int x1, int y1) { return sqr(x-x1)+sqr(y-y1) <= sqr(radie); } }
Vi har lagt till en konstant med namnet radie
. Tidigare var diametern
inkodad i löpande programtext, men nu definieras den så:
static final int radie = 5;Numeriska konstanter som dyker upp utan förklaring i programkod kallas ibland magic numbers. Det brukar vara en bra idé att undvika sådana. Ge hellre konstanten ett namn, som jag gjort här.
Notera att draw()
har ändrats. Den ritar numera en cirkel med centrum
vid position (x,y)
.
Metoden rymmer()
använder Pythagoras sats för att avgöra om koordinaten
(x1, y1)
hamnade inom punkten.
public static final int sqr(int x) { return x*x; } public boolean rymmer (int x1, int y1) { return sqr(x-x1)+sqr(y-y1) <= sqr(radie); }
Vi har två nya metoder i klassen RitPanel
. Först
hittaPunkt
.
public Punkt hittaPunkt(int x, int y) { for (Punkt p : punktMängd) { if (p.rymmer(x,y)) { return p; } } return null; }
Metoden hittaPunkt
tar en koordinat och letar efter en punkt som
rymmer den koordinaten. Om en sådan hittas returneras den. I annat
fall returneras null
.
Tidigare var ritpanelen sin egen muslyssnare, men eftersom koden för att hantera mushändelser har blivit mer komplex flyttar jag ut den till en egen klass:
class MusLyssnare extends MouseAdapter implements MouseMotionListener { private Ritpanel ritpanel; private Punkt utvald; public MusLyssnare (Ritpanel p) { ritpanel = p; } public void mouseClicked (MouseEvent e) { int x = e.getX(); int y = e.getY(); Punkt p = ritpanel.hittaPunkt(x,y); if (p == null) { p = new Punkt(e.getX(), e.getY()); ritpanel.addPunkt(p); } else { ritpanel.taBortPunkt(p); } ritpanel.repaint(); } public void mousePressed (MouseEvent e) { } public void mouseDragged (MouseEvent e) { } }
Det som är nytt är att om man klickar på en punkt tas den bort.
Om ni provkör (och har testat de föregående versionerna) kommer ni att uppskatta att punkterna numera hamnar exakt där man klickar. Prova att skapa två punkter som överlappar och se vad som händer när ni tar bort den ena.
I version 4 (program/swing-2/Punkter_4.java) har jag lagt till en metod
i klassen Punkt
för att flytta en punkt till en ny position:
public void flytta(int x1, int y1) { x = x1; y = y1; }
I klassen ritpanel har vi en relaterad metod:
public void flyttaPunkt (Punkt p, int x, int y) { p.flytta(x, y); }
De stora förändringarna i denna version kommer i klassen
MusLyssnare
. Vi introducerar en instansvariabel utvald
. Om användaren
håller på att flytta en punkt är utvald
bunden till denna punkt.
Om användaren pressar ner musknappen över en punkt blir den utvald:
public void mousePressed (MouseEvent e) { int x = e.getX(); int y = e.getY(); utvald = ritpanel.hittaPunkt(x,y); }
När användaren drar musen med knappen nedtryckt flyttas den utvalda punkten:
public void mouseDragged (MouseEvent e) { int x = e.getX(); int y = e.getY(); if (utvald != null) { ritpanel.flyttaPunkt(utvald,x,y); } ritpanel.repaint(); }
Jag hoppas att ni tar er tid att testköra de olika versionerna av det här enkla ritprogrammet. Prova också att flytta några punkter.