Göm meny
Gäller för: HT23

Temauppgift 5

Temauppgift 5 består av två delar. Del 1 är en introduktion till grafiska gränssnitt och händelsedriven programmering. I Del 2 ska ni skriva en egen algoritm som placerar ut kvadrater i ett fönster med olika strategier.

  • Del 1: Grafiska gränssnitt och händelsedriven programmering med Tkinter. Redovisas samt kodinlämning
  • Del 2: Layout av kvadrater i fönster. Redovisas samt kodinlämning

Redovisning, inlämning och kompletteringar

  • Allmän information om den muntliga redovisningen, samt eventuella kompletteringar kan ni läsa om sidan Redovisning.
  • Ni får antingen 1 poäng, 3 poäng eller Komplettering på inlämningar som hör ihop med temauppgift 4-6. Vid Komplettering får ni instruktioner om vad som ska kompletteras.

Att redovisa

  • Del 1
    • Visa att Uppgift 2 och Uppgift 3 fungerar
    • Gå kort igenom hur er layout av Uppgift 2 är uppbyggd
    • För Uppgift 3, berätta programflödet för när man trycker på knappen. Hur får man informationen från fönstret med checkboxarna och radioknapparna till fönstret med enbart textrutan?
  • Del 2
    • Demonstrera att er kod fungerar som den ska.

Var förberedda så att ni hinner med redovisningen på 10 minuter!

Poäng

För 1 poäng på Temauppgift 5 ska följande göras:

För 3 poäng på Temauppgift 5 ska följande göras:

Kodkrav

PEP8 och PEP257 ska följas

  • all kod följer PEP8-standarden
  • all kod följer PEP257

Funktionskommentarer (PEP257) ska inte beskriva hur ni implementerat funktionerna, utan vad funktionerna gör och hur man använder dem (förklara argumenten).

Se Tips: Kontrollera PEP 8 och PEP 257 för tips om hur du kontrollerar för PEP8 och PEP257 automatiskt.

Variabelnamn och kommenterade satser som är längre än 1 rad

  1. Inga variabelnamn får bestå av endast en enskild bokstav.
  2. Alla satser som består av två eller fler rader ska ha en tillhörande kommentar. T.ex. if-satser och loopar. Kommentaren ska inte vara en “innantilläsning” av koden den hör till. Använd rätt kommentarsmarkering (t.ex. """ för funktions- metodkommentarer, # för kommentarer i löpande kod, se PEP8 och PEP257).

Undantag till 2. ovan är satsen som är längre än 2 rader är den enda satsen i en funktion. Där räcker funktionskommentaren.

Anmärkning: Krav 2 reflekterar inte nödvändigtvis kommentarskrav “i verkligheten” utan finns som krav i kursen för att ni ska öva er på att skriva kommentarer som förklarar kod.

Här är exempel på icke-godkänd kod:

y = 0
g = 0

# för alla element i listan, lägg till x till y
for x in [19, 21, 24, 20]:
    y += x
g = y/4

Här är en godkänd variant:

age_tot = 0
age_avg = 0
ages = [19, 21, 24, 20]

# räkna ut summan av alla åldrar
for age in ages:
    age_tot += age
age_avg = age_tot/len(ages)

Ett undantag är där x och y används för x- och y-koordinater. Om ni har olika separata objekt med egna x- och y-koordinater bör ni dock markera detta (dvs återanvänd inte samma variabelnamn för olika ändamål):

Ej godkänd kod

x = 100
y = 0
hero.set_position(x, y)
x = 500
y = 0
monster.set_position(x, y)

Godkänd kod

x = 100
y = 0
hero.set_position(x, y)

monsterx = 500
monstery = 0
monster.set_position(monsterx, monstery)

Inga hårdkodade värden

Minimera användningen av hårdkodade siffror, använd istället variabler med förklarande namn.

Variabler istället för hårdkodade värden

Som tumregel kan tänka att om ni använder samma siffra på fler än ett ställe i samma syfte, så ska ni ersätta den med en variabel.

En annan anledning att byta ut en hårdkodad siffra mot en variabel är för att enklare kunna experimentera med olika värden genom att ändra variabelvärden som samlats på ett smidigt ställe i koden.

Om ni tänker “Men det är så svårt att komma på vad vi ska döpa den till”; då borde ni även inse att det är ännu svårare för en utomstående att ens förstå vad syftet med siffran är.

Användning av hårdkodade siffervärden (även kallade för “magic numbers”) ses generellt sett som ett anti-mönster, dvs ett mönster man ska undvika. Läs mer om detta på t.ex. Wikipedia.

Exempel på kod som ej får godkänt

kontosaldo = 2000
att_betala = 500 - (500 * 0.1)
att_betala += 240 - (240 * 0.1)
kontosaldo -= att_betala

Kod som får godkänt

kontosaldo = 2000
pris_godis = 500
pris_godispåse = 240
rabatt = 0.1
att_betala = pris_godis - (pris_godis * rabatt)
att_betala -= pris_godispåse - (pris_godispåse * rabatt)
kontosaldo -= att_betala

Rekommendation: Dela upp er kod

  • Dela upp er kod i delfunktioner vid behov:
    • när ni stöter på kod som ni upprepar, bryt ut koden (lägg koden i en funktion)
    • skicka nödvändig information som argument till funktionen och låt den returnera eventuellt resultat, använd inte globala variabler
    • dela även upp er kod i fler funktioner när den blir för lång

Som tumregler, försöka se till att era funktioner

  • endast gör en sak
  • inte är längre än 15 rader (gärna kortare) exklusive kommentarer

Uppgiftsnivåer och betyg

Se sidan Betyg och kursmoment för information om kraven för att få VG på kursmomentet LAB2 (Temauppgift 4-6).

Uppgradera temauppgiftsbedömning

  • Temauppgift 3: Om ni redan fått Godkänt, men vill ha Väl godkänt, boka tid med en handledare för ett försök att redovisa det som behövs för Väl godkänt.
  • Temauppgift 4-5: Om ni redan har fått 1 poäng, men vill ha 3 poäng boka tid med en handledare för ett försök att redovisa det som behövs för 3 poäng.
  • Temauppgift 6: Om ni redan har fått 1 poäng, men vill sikta på 3 poäng på temauppgift 6. Skicka ett e-postmeddelande till Jody.

OBS! Deadlines finns för ovanstående.

Del 1 - Grafiska gränssnitt med Tkinter

Syftet med Del 1 är att ni ska bekanta er med ramverket TkInter, hur gränssnitt skapas på ett objektorienterat sätt och hur man hanterar användarinteraktion på ett händelsebaserat sätt. För dessa uppgifter ska ni använda en fil per uppgift. Den första uppgiften löser ni t.ex. i filen del1_uppg1.py.

Tips: Ta hjälp av tkinter-exempelkoden från föreläsningen.

OBS! Se även kod- och kommentarskraven ovan: allmänna kodkrav, kodkrav 3 poäng.

Referensdokument

Ni kommer behöva slå upp saker i referensdokumentation:

Uppgift 1 - Ett fönster och en knapp

I den första uppgiften ska ni skapa ett fönster som innehåller en knapp. När man klickar på den knappen ska texten "Knappen funkar!" skrivas ut i terminalen.

Uppgift 2 - Layout

I denna uppgift ska ni göra följande:

  • Skapa ett fönster och placera ut TkInter-widgets enligt nedanstående layoutskiss. Den behöver inte vara exakt likadan med avseende på exakta storlekar, men elementens placering relativt varandra ska vara som på bilden.
  • Ta fram en lösning som använder antingen geometri-hanteraren grid eller pack.
  • Knapparna ska inte göra något när man klickar på dem.

Tips! Kom ihåg att ni kan gruppera widgets genom att lägga dem i en frame och sedan lägga till det Frame-objektet till huvudfönstret.

layoutskiss

Geometri-hanterare (Geometry managers)

Det finns tre geometri-hanterare att välja mellan i TkInter. Syftet med geometri-hanterare (kallas ibland för layout-hanterare/layout managers i andra språk) är att hjälpa till och till viss mån automatisera hur och var GUI-komponenter placeras. Läs om dessa i mer detalj här:

Principen som används när man skapar ett GUI med TkInter är att man först skapar en widget och då kopplar ihop den med den komponent som är dess behållare. Sedan anropar den geometri-hanterar metod i widgeten som man vill använda och skickar då med de parametrar som ska tas hänsyn till när widgeten placeras ut.

Uppgift 3 - Checkboxar, radioknappar och text

I denna uppgift ska ni skapa ett fönster som har samma layout som bilden nedan. När man klickar på knappen ska ett nytt fönster skapas som innehåller ett text-fönster. Text-fönstret ska innehålla alla valda alternativ från huvudfönstret, dvs

  • texten från text-fältet,
  • alla markerade checkboxar,
  • den valda radioknappen (endast en radio-knapp ska kunna vara vald åt gången)

Checkboxar och radioknappar

Resultatfönster

Se checkbox.py, radio.py och text.py för exempelkod.

Del 2 - Layout av kvadrater i fönster

I denna uppgift ska ni skapa en algoritm som får in en lista med tkinter.Label-objekt, samt höjd och bredd på den tkinter.Frame som de placeras ut i. tkinter.Label-objekten ska placeras ut i sekvens, utan att de nuddar varandra. Varje kvadrat ska synas helt och hållet.

Krav för både 1 och 3 poäng

  • Algoritmen ska fungera för olika storlekar på layouttestar-fönstret!
  • Ett större fönster ska innehålla fler kvadrater än ett mindre fönster
  • Endast så många kvadrater som får plats ska placeras ut, dvs sluta placera ut kvadrater när utrymmet tar slut.
  • Kvadraterna behöver inte centreras i fönstret, dvs det kan vara olika avstånd mellan kvadraterna till längst till vänster och den vänstra kanten och kvadraterna längst till höger och den högra kanten.

Funktionalitet, 1 poäng: Alla kvadrater är lika stora

OBS! Kontrollera vilken algoritmvariant ni ska implementera. Olika grupper ska implementera olika varianter. Se slutet av sidan.

För att få 1 poäng på temauppgiften ska algoritmen fungera när alla Label-objekt (kvadraterna) i den givna listan har samma storlek. Storleken på kvadraterna kan variera mellan olika listor av kvadrater (dvs att en lista kan innehålla kvadrater som t.ex. är 30x30 stora, medan en annan lista kan innehålla kvadrater som t.ex. är 60x60 stora (alla alternativ utom random i gränssnittet).

exempel på layout för 1 poäng

OBS! Se även kod- och kommentarskraven ovan.

Funktionalitet, 3 poäng: Varierande storlek på kvadrater

OBS! Kontrollera vilken algoritmvariant ni ska implementera. Olika grupper ska implementera olika varianter. Se slutet av sidan.

För att få 3 poäng ska algoritmen även fungera för listor som innehåller kvadrater av blandad storlek, dvs när man använder alternativet random på kvadratsstorlek i gränssnittet. Kvadraterna ska placeras så att de är centrerade i tänkta rader och kolumner (se bild).

exempel på layout för 3 poäng

OBS! Se även kod- och kommentarskraven ovan.

Tips!

Fundera på hur ni ska angripa problemet:

  • Vad för information behöver ni för att kunna räkna ut vilken X- och Y-position som ni ska placera varje enskild kvadrat på.
  • Hur kan ni ta fram den informationen?
  • Testa er strategi på papper först innan ni börjar programmera för att se om den fungerar.
  • Innan ni börjar koda, läs igenom exempelkoden (se avsnittet kodskelett ovan).
  • Skriv ner er algoritm som kommentarer i er fil så att ni kan referera till dem när ni programmerar.

Bilder från lektionen hittar ni under lektionssidan.

Kodskelettet

När ni ska börja koda hittar ni de filer som hör till labben i /courses/729G46/kursmaterial/temauppg5/del2.

Filen tema5.py innehåller koden som skapar layout-testaren. Ni behöver inte ändra i den filen, men om ni vill kan ni titta i den för exempel på hur man kan strukturera kod för grafiska gränssnitt, samt om ni vill se hur och var layoutfunktionen används.

random-layout.py är ett exempel på en algoritm som placerar ut kvadraterna slumpmässigt hittar ni i random-layout.py. I den ser ni hur en instans av klassen tema5.LayoutTester skapas och får layoutfunktionen som ett argument.

I uppgiften ska ni utgå från koden i random-layout.py. Läs denna fil och testkör den för att orientera er i kodupplägget.

Ni testkör den med:

$ python3 random-layout.py

Layout-testarfönstrets storlek kommer att vara olika varje gång programmet startas. Kvadratsstorleksvalen slumpas också för varje gång programmet startas.

Utplacering av kvadraterna (tkinter.Label-objekt)

square.place(x=100, y=50)

I ovanstående exempel refererar variabeln square till en tkinter.Label-instans. I exemplet används metoden .place() med de namngivna argumenten x och y för att placera ut instansen 100 enheter från den vänstra kanten och 50 enheter från den övre kanten. Placeringen av en Label mäts från dess övre vänstra hörn. Origo i koordinatsystemet som används ligger alltså högst upp till vänster, med en y-axel som pekar nedåt, och en x-axel som pekar åt höger.

Ni ska inte använda funktionalitet från geometri-hanteraren Place (som t.ex. anchor), utan kvadraterna ska placeras ut endast med .place(x=xpos, y=ypos).

Kvadraternas storlek

För att ta reda på hur hög en tkinter-widget är, kan man använda sig av metoden .winfo_height(). För bredden använder man .winfo_width.

# spara kvadratens höjd och bredd
square_height = square.winfo_height()
square_width = square.winfo_width()

I ovanstående kod är square ett tkinter.Label-objekt.

Argument till layout-funktionen

Er algoritm implementerar ni genom att skriva en funktion som tar in nedanstående parametrar:

  • squares: Lista som innehåller tkinter.Label-objekt
  • frame_height: Höjden (int) på den Frame som kvadraterna ligger i.
  • frame_width: Bredden (int) på den Frame som kvadraterna ligger i.
  • start_left: Värdet True betyder att kvadraterna ska placeras ut från vänster sida. Värdet False betyder att kvadraterna ska placeras ut från höger.
  • start_top: Värdet True betyder att kvadraterna ska börja placeras ut uppifrån. Värdet False betyder att kvadraterna ska placeras ut från botten.

Funktionen skickar ni sedan till den gränssnittsinstans som skapas (se koden i random_layout.py som ni kan utgå ifrån).

När man klickar på knappen i gränssnittet skapas kvadraterna som ska layoutas enligt inställningarna i gränssnittet, därefter anropas den layoutfunktion som angavs när gränssnittet när det skapades. I funktionsanropet till layoutfunktionen skickar gränssnittet med listan med kvadrater, höjd, bredd, samt information från checkboxarna i gränssnittet som berättar i vilket hörn er layout-algoritm ska börja.

Grupptilldelning av algoritmvarianter

Det finns tre olika algoritmvarianter i Del 2. Vilken variant ni ska implementera beror på ert gruppnummer i Webreg (i de fall det finns ett par med gruppnummer högre än 3 gäller gruppnummer % 3, dvs gruppnummer 4 gör samma variant som grupp 1, 5 gör samma som grupp 2, osv.).

Grupp 1: row_layout

Döp er layout-funktion till row_layout. row_layout-algoritmen ska placera ut kvadraterna radvis. Det ska finnas mellanrum mellan de utplacerade kvadraterna. Bilden nedan visar placering som börjar från vänster och uppifrån.

row_layout

  • Algoritmen ska placera ut kvadraterna radvis.
  • När en rad är full påbörjas en ny rad.
  • Första kvadraten på varje rad placeras alltid ut i samma kolumn som den allra första kvadraten som placerades ut.
  • Algoritmen ska anpassa antalet kvadrater som placeras ut på en rad till storleken på fönstret, dvs det ska inte vara ett fast antal kvadrater som placeras ut på en rad.
  • Algoritmen ska endast placera ut kvadrater om de får plats i fönstret.
  • Argumentet start_top bestämmer om raderna läggs ut uppifrån och ner (start_top == True) eller nerifrån och upp (start_top == False).
  • Argumentet start_left bestämmer om kvadrater i en rad placeras ut från vänster till höger (start_left == True) eller från höger till vänster (start_left == False).

För en komplett lista på vilka argument som funktionen tar emot, läs avsnittet argument till layout-funktionen ovan.

Grupp 2: column_layout

Döp er layout-funktion till column_layout. column_layout-algoritmen ska placera ut kvadraterna kolumnvis. Det ska finnas mellanrum mellan de utplacerade kvadraterna. Bilden nedan visar placering som börjar från vänster och uppifrån.

column_layout

  • Algoritmen ska placera ut kvadraterna kolumnvis.
  • När en kolumn är full påbörjas en ny kolumn.
  • Första kvadraten i varje kolumn placeras alltid ut på samma rad som den allra första kvadraten som placerades ut.
  • Algoritmen ska anpassa antalet kvadrater som placeras ut en en kolumn till storleken på fönstret, dvs det ska inte vara ett fast antal kvadrater som placeras ut i en kolumn.
  • Algoritmen ska endast placera ut kvadrater om de får plats i fönstret.
  • Argumentet start_left bestämmer om kolumner placeras ut från vänster till höger (start_left == True) eller från höger till vänster (start_left == False).
  • Argumentet start_top bestämmer om kvadraterna i en kolumn läggs ut uppifrån och ner (start_top == True) eller nerifrån och upp (start_top == False).

För en komplett lista på vilka argument som funktionen tar emot, läs avsnittet argument till layout-funktionen ovan.

Grupp 3: zigzag_layout

Döp er layout-funktion till zigzag_layout. zigzag_layout-algoritmen ska placera ut kvadrater radvis, men när den kommer till slutet av en rad ändras riktningen som kvadraterna i nästa rad läggs ut i. Det ska finnas mellanrum mellan de utplacerade kvadraterna. Bilden nedan visar placering som börjar från vänster och uppifrån.

zigzag_layout

  • Algoritmen ska placera ut kvadraterna radvis.
  • När en rad är full påbörjas en ny rad.
  • När en ny rad påbörjas växlar utplaceringsordningen. Om föregående rad av kvadrater placerades ut från vänster till höger, placeras nästa rad av kvadrater ut från höger till vänster och vice versa.
  • Första kvadraten på varje rad placeras ut i samma kolumn som den sista kvadraten på föregående rad.
  • Algoritmen ska anpassa antalet kvadrater som placeras ut på en rad till storleken på fönstret, dvs det ska inte vara ett fast antal kvadrater som placeras ut i en rad.
  • Algoritmen ska endast placera ut kvadrater om de får plats i fönstret.
  • zigzag_layout-algoritmen lägger alltid ut rader uppifrån och ner, oavsett värde på start_top.
  • Argumentet start_left bestämmer om kvadraterna i första raden placeras ut från vänster till höger (start_left == True), eller om kvadraterna i första raden placeras ut från höger till vänster (start_left == False)

För en komplett lista på vilka argument som funktionen tar emot, läs avsnittet argument till layout-funktionen ovan.

Extra utmaningar (frivilliga)

Här kommer några extra uppgifter att göra för de som vill.

Autocomplete med UI

Skapa ett fönster med en liten textruta där man kan skriva in text. Nedanför textrutan ska de 5 mest frekventa orden som börjar på användarens text visas. Använd data från Temauppgift 2.

Förslagen ska uppdateras för varje förändring som sker i användarens textruta.

Tips: Använd en tkinter.Entry som användarens textruta och en tkinter.Label för att visa förslagen.

Tips: Innehållet i ett tkinter.Entry-objekt är ett StringVar-objekt. Med hjälp av metoden .trace() hos StringVar-objekt kan vi säga att en viss funktion ska köras när förändringar sker i objektet. Se The Variable Classes


Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2023-11-14