Rutenett
I denne lab'en skal vi tegne et rutenett som vist over. Oppgaven består i hovedsak av tre deler:
- Skriv en klasse
ColorGrid
som representerer et rutenett av farger. - Skriv en klasse
CellPositionToPixelConverter
som har en metode som regner ut piksel-koordinatene for en gitt rute. - Skriv en klasse
GridView
som kan tegne et rutenett av farger.
- Anbefalte forberedelser
- Bli kjent med utlevert kode
- Opprett rutenett av farger
- Tegning
- Opprett rutetnettet som skal tegnes
- CellPositionToPixelConverter
- drawGrid
- drawCells
- Bonusoppgave
Anbefalte forberedelser
- Laben antar at du har kjennskap til grunnleggende java, inkludert grensesnitt, klasser og objekter.
- Du bør ha skummet igjennom kursnotatene om arv, men vi kommer ikke til å gå i dypden på dette i denne lab'en.
- I den utleverte koden dukker det opp to record-klasser. Du trenger ikke sette deg inn i mutabilitet for denne lab'en, men kursnotatene om dette inneholder også et avsnitt om record-klasser hvor det er verdt å skumme gjennom eksempelet som demonstrerer bruken.
- Kursnotatene om grafikk vil naturligvis være spesielt relevante, primært de tre første avsnittene om rammeverket, koordinatsystemet og grunnleggende figurer; men også hjelpemetoder, farger og adaptiv tegning blir adressert i lab'en.
Bli kjent med utlevert kode
Undersøk filene i pakken no.uib.inf101.colorgrid
, og besvar spørsmålene i TextQuestions
(link).
✅ Du er klar til gå videre når alle testene i TestTextQuestions
(link) passerer.
Opprett rutenett av farger
I denne oppgaven skal du lage klassen ColorGrid
(link). Den er nå helt tom.
-
La klassen implementere grensesnittet
IColorGrid
-
La klassen ha en konstruktør med to parametre: en int som beskriver antall rader, og en int som beskriver antall kolonner. Standard-verdien til en posisjon i rutenettet (før
set
-metoden har blitt kalt på gitt posisjon) skal værenull
. -
Fyll ut metodene du trenger i overenstemmelse med javadoc-kommentarene til
IColorGrid
.
Du kan leke deg litt i Main::main
for å sjekke for deg selv at klassen fungerer som du forventer. For eksempel:
// Opprett et rutenett med 3 rader og 4 kolonner
IColorGrid grid = new ColorGrid(3, 4);
System.out.println(grid.rows()); // forventer 3
System.out.println(grid.cols()); // forventer 4
// Sjekk at standard-verdien er null
System.out.println(grid.get(new CellPosition(1, 2))); // forventer null
// Sjekk at vi kan endre verdien på en gitt posisjon
grid.set(new CellPosition(1, 2), Color.RED);
System.out.println(grid.get(new CellPosition(1, 2))); // forventer rød
System.out.println(grid.get(new CellPosition(2, 1))); // forventer null
Husk å rydde opp etter deg i Main::main
når du er ferdig!
✅ Du er klar til gå videre når alle testene i TestColorGrid
(link) passerer.
.
Tegning
Ha kursnotatene om grafikk i bakhodet når du gjør denne oppgaven.
I GridView
:
-
La klassen utvide
JPanel
-
La konstruktøren til
GridView
sette standard størrelse på lerretet til 400x300 piksler -
Overskriv metoden
public void paintComponent(Graphics g)
. Begynn med å kalle på super-metoden og opprett en Graphics2D -variabel fra g, slik som vist i kursnotatene om grafikk. - Velg din favoritt-figur fra kursnotatene og tegn den i paintComponent (midlertidig, fjern den igjen når vi senere skal tegne rutenettet )
I Main
:
-
Opprett et
GridView
-objekt -
Opprett et
JFrame
-objekt -
Kall
setContentPane
-metoden på JFrame-objektet med GridView-objektet som argument -
Kall
setTitle
,setDefaultCloseOperation
,pack
ogsetVisible
på JFrame-objektet etter mønster fra kursnotatene om grafikk.
✅ Du er klar til å gå videre hvis du ser tegningen din i et vindu når du kjører Main
.
Opprett rutenettet som skal tegnes
I Main::main
skal vi nå opprette et rutenett, og gi det til GridView
-konstruktøren som et argument ved opprettelse. Deretter skal vi endre GridView slik at den tegner dette rutenettet.
-
I
Main::main
, opprett et ColorGrid-objekt med 3 rader og 4 kolonner. Sett fargene i hjørnene til å være- Rød i hjørnet oppe til venstre (posisjon (0, 0))
- Blå i hjørnet oppe til høyre (posisjon (0, 3))
- Gul i hjørnet nede til venstre (posisjon (2, 0))
- Grønn i hjørnet nede til høyre (posisjon (2, 3))
-
I
GridView
, legg til en parameter av typenIColorGrid
i konstruktøren, og legg også til en instansvariabel av samme type. Initialiser feltvariabelen med argumentet gitt til konstruktøren.
For å tegne rutenettet gjenstår det å endre på paintComponent -metoden. Vi skal benytte oss av tre hjelpemetoder for å tegne rutenettet. I paintComponent gjør vi et kall til
-
drawGrid
, som har som ansvar å tegne et fullstendig rutenett, inkludert alle rammer og ruter (alt innenfor det grå området i illustrasjonen). For å tegne selve rutene, kaller denne metoden på -
drawCells
, som har som ansvar å tegne en samling av ruter. For hver rute regner denne metode ut hvor ruten skal være ved å kalle på hjelpemetoden -
getBoundsForCell
som vet hvordan å regne ut posisjonen til én rute i rutenettet.
Det viser seg at det er den sistnevnte metoden som er mest komplisert. Vi kunne hatt getBoundForCell
som en vanlig hjelpemetode, men siden dette er en relativt isolert operasjon som vi ønsker å kunne teste separat, oppretter vi en en klasse for denne hjelpemetoden: CellPositionToPixelConverter.
Måten vi tenker på når vi skal utvikle et program er «top-down» -- man begynner med å dele opp oppgaven i store steg, og så drømmer vi opp hjelpemetoder vi trenger før disse hjelpemetodene faktisk eksisterer. Når vi faktisk koder, er det ofte lettest å gjøre det «bottom up», slik at vi kan teste hver enkelt byggeklosse/metode mens vi holder på. Dette for å si: hvilken rekkefølge du løser resten av oppgaven er opp til deg. Det kan være at det er lettere å hoppe litt frem og tilbake mellom de neste avsnittene.
CellPositionToPixelConverter
Vi ønsker å opprette en hjelpemetode getBoundsForCell
som oversetter koordinater i rutenettet til et rektangel med posisjon og størrelse beskrevet som piksler til bruk på et lerret. Det er naturlig at denne metoden
- har en parameter av typen
CellPosition
og - returnerer et
Rectangle2D
-objekt.
Men -- dette er ikke tilstrekkelig informasjon for å gjøre utregningen; vi trenger i tillegg å vite
- innefor hvilket område rutenettet befinner seg
- hvor mange rader og kolonner det er i rutenettet som helhet, og
- hvor stor avstanden mellom rutene skal være.
Disse siste delene med informasjon vil ikke endre seg særlig fra kall til kall, men er en del av konteksten metoden kjører i. Slik kontekst er best beskrevet som feltvariabler.
I klassen CellPositionToPixelConverter
:
-
Opprett instansvariabler:
- Et
Rectangle2D
-objektbox
som beskriver innenfor hvilket område rutenettet skal tegnes - Et
GridDimension
-objektgd
som beskriver størrelsen til rutenettet rutene vil være en del av - En
double
kaltmargin
som beskriver hvor stor avstanden skal være mellom rutene
- Et
-
Opprett en konstruktør i klassen med tre parametre: et
Rectangle2D
-objekt, etGridDimension
-objekt og endouble
. Initaliser feltvariablene med argumentene som mottas i konstruktøren. -
Opprett metoden
getBoundsForCell
med en parameter av typenCellPosition
(i figur under navgittcp
) og returtypeRectangle2D
.
Returverdien er et Rectangle2D
-objekt. For å opprette dette objektet, må du regne ut fire verdier: x, y, bredde og høyde for den gitte ruten. Så kan du returnere et nytt Rectangle2D.Double
-objekt med disse verdiene.
Illustrasjonen under visualiserer parameterne og resultatvariablene. Variabler i svart tekst er gitt som input eller er tilgjengelig som feltvariabler, mens variablene i rød kursiv tekst er de du skal regne ut og returnere.
Hint:
- Benytt
double
hvis du gjør regnestykker som involverer divisjon, da unngår du avrundingsfeil. - Ikke bland horisontale og vertikale verdier. Horsiontale begreper: x, bredde, kolonne. Vertikale begreper: y, høyde, rad.
- Begynn med å regne ut cellWidth og cellHeight, og bruk verdiene du finner der for videre kalkulasjoner.
Eksempel: anta at du får følgende parametre (som i illustrasjonen):
box.getX()
er 30 ogbox.getY()
er 30box.getWidth()
er 340 ogbox.getHeight()
er 240cellPosition.col()
er 2 ogcellPosition.row()
er 1gd.cols()
er 4 oggd.rows()
er 3margin
er 30Vi begynner med å regne ut
cellWidth
. Siden vi har 4 kolonner totalt, vil det gå med 5*30=150 piksler til marginer, og vi får da 190 piksler igjen å fordele på de fire kolonnene. Vi får da at cellen skal ha bredde 47.5.For å finne verdien til
cellX
begynner vi på posisjonenbox.getX()
og går derfra videre mot høyre ved å plusse på margin + rutebredde + margin + rutebredde + margin. Verdien blir da 30+30+47.5+30+47.5+30 = 215.Tilsvarende finner vi at
cellHeight
blir 40 ogcellY
blir 130.
✅ Du er klar til å gå videre når testene i TestCellPositionToPixelConverter
passerer.
drawGrid
Denne metoden i GridView
skal ha et Graphics2D
-objekt som parameter, og ikke ha noen returverdi. Planen er å først tegne en stor grå firkant, og så tegne selve rutene «oppå».
-
Opprett et Rectangle2D -objekt med en fast 30 pikslers avstand til kanten på vinduet (se avsnitt om fast avstand til kantene på lerretet i kursnotater om grafikk)
- Det kan være lurt å lagre tallet som en konstant med et beskrivende navn (altså opprett en static final feltvariabel
private static final double OUTERMARGIN = 30;
og så brukeOUTERMARGIN
i stedet for 30 når du kommer tilbake her)
- Det kan være lurt å lagre tallet som en konstant med et beskrivende navn (altså opprett en static final feltvariabel
-
Fyll rektangelet med gråfarge på lerretet.
- Det kan være lurt å lagre fargen som en konstant med et beskrivende navn (altså opprett en static final feltvariabel
private static final Color MARGINCOLOR = Color.LIGHT_GRAY;
og så brukeMARGINCOLOR
i stedet for Color.LIGHT_GRAY når du kommer tilbake her)
- Det kan være lurt å lagre fargen som en konstant med et beskrivende navn (altså opprett en static final feltvariabel
-
Opprett et
CellPositionToPixelConverter
-objekt.- Hvilket objekt med typen GridDimension skal du bruke som argument ved opprettelsen? Har du et slik objekt liggende et sted allerede?
- Marginen skal være et fast tall; i eksempelillustrasjonen er tallet 30 blitt brukt. Det kan være lurt å lagre tallet som en konstant med et beskrivende navn.
-
Gjør et kall til en hjelpemetoden
drawCells
beskrevet under
PS: Siden metoden benytter instansmetoder kan metoden ikke være static. Siden metoden ikke skal benyttes av noen utenfor GridView
-klassen, bør metoden være private.
drawCells
Denne metoden i GridView
er uten returverdi, men skal ha tre parametre:
- et
Graphics2D
-objekt, lerretet rutene skal tegnes på - et
CellColorCollection
-objekt, rutene som skal tegnes - et
CellPositionToPixelConverter
-objekt som kan regne ut rutene sin posisjon
La metoden iterere gjennom rutene i CellColorCollection -objektet, og tegne hver av dem på lerretet. Dersom fargen er null, bruk Color.DARK_GRAY
i stedet.
PS: siden drawCells ikke er avhengig av instansvariabler, bør metoden være static. Siden den ikke benyttes utenfor GridCell
-klassen bør metoden være private.
✅ Du er ferdig med lab'en når testene i TestGridView
passerer, og du kan kjøre Main-metoden og ser samme bilde som vist i illustrasjonen nå programmet kjører. Det skal fungere å endre størrelse på vinduet, og rutenettet skal strekke seg for å fylle hele lerretet med en fast avstand til kanten.
Bonusoppgave
-
La klassen
BeautifulPicture
i pakkenno.uib.inf101.bonus
utvideJPanel
og tegn en vakker tegning med utgangspunkt i kursnotatene for grafikk. BenyttMain
-klassen i samme pakke for å starte programmet.
Det blir en liten premie til det peneste bildet. Jury'en legger vekt på:
- at bildet er vakkert, og
- at bildet er adaptivt, det vil si: tilpasser seg størrelsen på vinduet på en pen måte, og
- at koden er vakker å lese.
Du er oppfordret til å bruke rikelig med hjelpemetoder og eventuelt også flere klasser som kan hjelpe til med tegningen dersom det er hensiktsmessig.