onsdag 27 mars 2013

En målarduk

För att visa olika former av grafik finns objektet Canvas.  Canvas använder ett koordinatsystem med origo i övre vänstra hörnet och en pixel per enhet. Y-axeln är riktad nedåt.

Exempel:

from tkinter import *

root = Tk()

w = Canvas(root, width=200, height=100, bg="white")
w.pack()

w.create_line(0, 0, 200, 100)
w.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))

w.create_rectangle(50, 25, 150, 75, fill="blue")

mainloop()

Metoder för att skapa objekt på en Canvas är:
  • create_line
  • create_rectangle
  • create_arc
  • create_oval
  • create_polygon
  • create_text
  • create_window
  • create_bitmap
  • create_image
Se sida 209-210 i boken samt här.

Som synes ovan kan du även konfigurera utseendet på olika objekt med hjälp av angivelser av färger (bg i koden ovan står för background colour) och andra attribut.

Uppgift 3.2
Rita en smiley med tkinters Canvas

Saker som rör sig och att reagera på musklick

Genom att binda en  händelsehanterare till en Canvas kan du göra olika saker vid musklick eller annat som användaren gör. Bindandet gör med metoden .bind("Namn", funktion).
Namn anger händelsen som ska bindas till funktionen. Se här för lista. Se även boken sid. 238-245
funktion är den funktion som ska köras vid händelsen. Alla händelsefunktioner har argumentet event som är en referens till händelseobjektet som innehåller data om händelsen.

Objekten som ritas på en Canvas kan modifieras och förflyttas efter att de ritats. Detta är möjlig då varje objekt ges ett unikt id-nummer i och med att det skapas. Vid förändringarna hänvisas till dessa id-nummer. Se även boken sid. 213-220

Nedanstående exempel ritar ut en bild med en grön prick. Om användaren klickar på musknappen flyttas pricken 10 pixlar snett nedåt.


from tkinter import *

root = Tk()
root.title('Dot')

def klick_b1(event):
    w.move(dot_id, 10, 10)

w = Canvas(root, width=200, height=200, bg="white")
w.bind("<Button-1>", klick_b1)
photo = PhotoImage(file="../greendot.gif")
dot_id=w.create_image(30, 30, image=photo)
w.pack()
mainloop()

Övning 3.7
Skriv in programmet ovan och testa. Bilden på den gröna pricken laddar du ner och placerar i din projektmapp:

  

Global eller klass

Funktioner kan normalt endast läsa globala variabler, inte ändra dem. Detta är ett problem för händelsebindningar i Python. Du skulle behöva skicka med informationen till funktionen som körs vid händelsen men tyvärr går inte det på ett enkelt sätt då man endast anger funktionens namn. Det finns två olika lösningar på problemet:

global

För att kunna ändra globala variabler i en funktion måste de deklareras först i funktionen med nyckelordet global. Ex:

from tkinter import *

root = Tk()
root.title('Dot')
x=30
y=30
dx=10
dy=10

def klick_b1(event):
    global x, y
    w.move(dot_id, dx, dy)
    x=x+dx
    y=y+dy
    print(str(x)+","+str(y))

w = Canvas(root, width=200, height=200, bg="white")
w.bind("<Button-1>", klick_b1)
photo = PhotoImage(file="../greendot.gif")
dot_id=w.create_image(x, y, image=photo)
w.pack()
mainloop()

Klass
Att använda global passar bra i mindre program men det är bättre att hantera objekt när programmen blir lite större. Genom att skapa en egen underklass till Canvas kan du lägga till de data och funktioner du behöver och hantera dessa. Denna lösning ger ser ut så här:

from tkinter import *


class Dot_Canvas(Canvas):
    
    def __init__(self, root):
        super().__init__(root, width=200, height=200, bg="white")
        self.bind("<Button-1>", self.klick_b1)
        self.x=30
        self.y=30
        self.dx=10
        self.dy=10
        self.photo = PhotoImage(file="../greendot.gif")
        self.dot_id=self.create_image(self.x, self.y, image=self.photo)
        self.pack()
        
    def klick_b1(self, event):
        self.move(self.dot_id, self.dx, self.dy)
        self.x=self.x+self.dx
        self.y=self.y+self.dy
        print(str(self.x)+","+str(self.y))

root = Tk()
root.title('Dot')
Dot_Canvas(root)
mainloop()

Lösningen med klass är som synes  "klumpigare" för små testprogram men har stora fördelar då programmen blir större. Den nya klassen Dot_Canvas innehåller allt den behöver för sin funktion och kan skapas enkelt i huvudprogrammet.

Använd valfri metod för att lösa nedanstående:

Övning 3.8
Ändra i programmet ovan så att punkten ser ut att studsa när den når sidorna på fönstret.


Uppgift 3.3
Händelseinformationen som finns i objektet event i koden ovan innehåller bland annat information om var muspekaren var när händelsen inträffade. Detta är lagrat som event.x och event.y. Använd exemplet ovan som grund till ett program som placerar den gröna pricken där användaren klickar.

onsdag 20 mars 2013

Textrutan Entry

Entry är en textruta för att skriva in enradstexter. Innehållet i textrutan sparas i en angiven variabel som är ett objekt av typen StringVar().  För att komma åt texten i textrutan används metoden get på textvariabeln. Se ex:

from tkinter import *

def ny_text(): 
    lbl.config(text=en1_text.get())
        
root=Tk()
root.title('Textruta')
root.minsize(200, 60)
en1_text = StringVar() #Variabeln som innehåller textrutans text
en1=Entry(root, textvariable=en1_text).pack(fill=X)
btn = Button(root, text ="Skriv", command = ny_text)
btn.pack(fill=X)
lbl = Label(root, text = "")
lbl.pack(fill=X)

root.mainloop()

För att ställa in texten i textrutan används metoden set("min text") på textvariabeln.

Övning 3.6
Ändra i programmet ovan så att knappen tömmer textrutan samtidigt som texten skrivs i etiketten.

Inlämningsuppgift 3.1
Använd ovanstående samt Övning 2.4 för att göra en grafisk version av ett program som löser andragradsekvationer och skriver ut svaret.

onsdag 13 mars 2013

Knappar som gör något

För att något ska hända när du trycker på en knapp anges vilket kommando (vanligen en funktion) som ska köras. Se exempel:

from tkinter import *

def sag_haj(): 
    lbl.config(text="Hej!")
        
root=Tk()
root.title('Count')
root.minsize(200, 100)
btn = Button(root, text ="Säg hej", command = sag_haj)
btn.pack(fill=X)
lbl = Label(root, text = "")
lbl.pack(fill=X)

root.mainloop()

Övning 3.4
Skriv in ovanstående program och testa,

Notera
I Python anges namnet på funktionen som skall köras. Den anropas inte. Där av endast namnet sag_haj utan () efter.

Global

För att kunna ändra globala variabler i en funktion måste du ange att du vill använda den globala variabeln mednyckelordet global anges före du utför ändringen. Detta är användbart när vi ska få knappar att göra saker om vi inte vill skapa objekt. Se nedan.

from tkinter import *

n=0

def oka_ett(): 
    global n
    n=n+1
    lbl.config(text=str(n))
        
root=Tk()
root.title('Count')
root.minsize(200, 100)
btn = Button(root, text ="+1", command = oka_ett)
btn.pack(fill=X)
lbl = Label(root, text = str(n))
lbl.pack(fill=X)

root.mainloop()


Genom att tala om att vi ska använda den globala variabeln n i funktionen oka_ett() kan vi utföra ändringen med kommandot från knappen.

Övning 3.5
Lägg till en knapp  i ovanstående program som minskar värdet på n med ett.

måndag 11 mars 2013

Placering i rutnät

Placering av etiketter och andra widgets i rutnät är en vanlig layoutmetod.

Nedanstående exempel skapar följande layout:



from tkinter import *

root = Tk()
root.title("tk")
root.minsize(200, 200)
#Skapar etiketter med ram
l1 = Label(root, text="Uppe Vänster", relief=RIDGE)
l2 = Label(root, text="Mitten Vänster", relief=RIDGE)
l3 = Label(root, text="Stor", relief=RIDGE)
l4 = Label(root, text="En längre text under", relief=RIDGE)
#Placerar in dem i rutnätet och anger att de ska fylla rutan
l1.grid(row=1, column=1, sticky=W+E+N+S)
l2.grid(row=2, column=1, sticky=W+E+N+S)
l3.grid(row=1, column=2,  columnspan=2, rowspan=2, sticky=W+E+N+S)
l4.grid(row=3, column=1, columnspan=3, sticky=W+E+N+S)

# Hur rader och columner ska uppföra sig vid ändring av fönsterstorleken
root.grid_columnconfigure(1, weight=1)
root.grid_columnconfigure(2, weight=1)
root.grid_columnconfigure(3, weight=1)
root.grid_rowconfigure(1, weight=1)
root.grid_rowconfigure(2, weight=1)
root.grid_rowconfigure(3, weight=1)

root.mainloop()

Se även boken sid. 165-166

Uppgift 3.1
Ändra i programmet ovan så att resultatet blir som i bilden nedan.

onsdag 6 mars 2013

Inroduktion till grafiskt gränssnitt

Vi använder det grafiska standardbiblioteket Tkinter. De objekt som det innehåller kallas widgets.

För att förenkla skapar vi en standardmodul som vi kan använda som mall när vi skapar våra objekt. Modulen sköter en del av det bakgrundsarbete som behövs.

Övning 3.1
En standardkod för att skapa grafiska objekt:

from tkinter import *

class myapp_tk(Tk):
    def __init__(self,parent):
        Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        pass #Här skrivs den kod som skapar de olika objekten i fönstret.

if __name__ == "__main__":
    app = myapp_tk(None)
    app.title('my application')
    app.mainloop()

Denna kos skapar ett widgetobjekt med namnet app och startar upp metoden mainloop(). Metoden mainloop är den loop som söter hantering av grafiken och reaktion på användarens inmatningar. Den är aktiv så länge objektet finns.

Att lägga till objekt i widgeten

Du lägger till objekt widgeten genom att först skapa ett nytt objekt som tillhör widgeten och sedan placera detta med metoden pack().

Övning 3.2
En etikett (label) med text. Byt ut innehållet i initialize(self) till:

    def initialize(self):
        self.label=Label(text="Hello World")
        self.label.pack()

Övning 3.3
Använd informationen i boken på sidan 158-163 för att testa pack-metoden och knappar.
 

tisdag 5 februari 2013

Moduler och arv

Klasser och moduler

Det går utmärkt att ha flera klasser i en modul. Om du då vill komma åt dessa klasser från en annan modul importerar du modulen. Som för funktioner skriver du sedan modulnamn.Klassnamn när du vill använda klassen. Ex:

import minmodul

...

# Skapar objektet mittobjekt av klassen MinKlass från modulen minmodul
mittobjekt = minmodul.MinKlass()

...

Övning 2.25
Kopiera klasserna för de geometrikska kropparna du skapat till en modul med namnet klossar (filnamnet är alltså klossar.py).

Arv eller klasser och underklasser

En av poängerna med objektorienterad programmering är arv av egenskaper mellan klasser. Vi kan skapa en huvudklass med de variabler och metoder som objekt i underklasser skall ha gemensamt.

Vi vill att alla klossar skall ha en position och en färg. Positionen skall vara en  lista med tre element [x, y, z] och färgen en textsträng. Vi vill kunna ändra färg och också ha möjlighet att flytta klossarna genom att ange en ny position.

Huvudklassen blir då:

#Huvudklass Klossar
class Kloss :
    def __init__(self):
        self.position =[0, 0, 0]
        self.farg=""
        
    def flytta(self,position):
        self.position=position
        
    def satt_farg(self, farg):
        self.farg=farg
  

Nu har vi grunden för metoder och variabler som kan ärvas av alla varianter av klossar vi skapar. Metoderna i underklasserna går före de i huvudklassen så då det finns en metod volym() i klassen Kub så körs den om objektet är en kub. Skulle det inte finnas en metod i underklassen så körs istället den i huvudklassen.Så om man kallar på metoden flytta för en kub så körs metoden i huvudklassen Klossar.

För att göra det lite snyggt  så inför vi tomma metoder i huvudklassen för de som ska implementeras i alla underklasser, alltså volym, area och skala. Genom detta är det lätt att se vad en underklass förväntas implementera. Tomma metoder skapas genom att lägga in pass som enda del i metodkroppen. Metoden skala(k) i huvudklassen Klossar ser alltså ut så här:

    def skala(self, k):
        pass

Övning 2.26
Lägg till tomma klasser för skala, volym och area i huvudklassen


Att skapa arvet

Nu måste vi få underklasserna att ärva metoder och variabler från huvudklassen.

För att tala om att en klass är en underklass till en annan anges huvudklassen i parentes bakom namnet på underklassen. I vårt fall ändrar vi i raden där vi definierar Kub till:

class Kub(Kloss) :

Nu är Kub en underklass till Klossar och har tillgång till dess metoder.För att Kub skall få samma variabler som huvudklassen ser vi till att huvudklassens initiering körs samtidigt som underklassens. Detta sker med hjälp av funktionen super(). super(). refererar till huvudklassen ungefär som self refererar till klassen själv.

Klassen Kub får med arv och initiering av variablerna i Klossar utseendet:

class Kub(Kloss) :
    
    def __init__(self, sida):
        super().__init__()
        self.sida=sida
        
    def skala(self, k):
        self.sida=self.sida*k
           
    def volym(self):
        return self.sida*self.sida*self.sida
    
    def area(self):
        return self.sida*self.sida*6

Övning 2.27
Skriv om övriga kloss-klasser så att de ärver metoder och variablerna från Klossar.

Övning 2.28
Skapa en ny modul, klosstest, och pröva att arvet fungerar:
Skriv och kör:

import klossar

kuben=klossar.Kub(8)
print(kuben.position)
kuben.flytta([1,2,3])
print(kuben.position)
kuben.skala(2)
print(kuben.volym())

Övning 2.29
Skriv tester för några av de andra kloss-typerna samt färgen också.

måndag 4 februari 2013

Övningar klasser

Vi ska skapa några klasser för att testa hur de fungerar. Vi skapar klossar av olika slag.
  • Kub
  • Rätblock
  • Pyramid - kvadratisk bas och symetrisk
  • Cylinder
  • Klot
En klass blir en ritning till var och en av klosstyperna. Vi vill förutom att skapa klossen även kunna få reda på dess volym och begränsningsarea.

Vi kan skapa alla klasserna i samma modul. Här är klassen för Kuben:

class Kub :
    
    def __init__(self, sida):
        self.sida=sida
        
    def volym(self):
        return self.sida*self.sida*self.sida
    
    def area(self):
        return self.sida*self.sida*6

Övning 2.22
Skapa klasser för de övriga klosstyperna i samma modul (fil). Tips: tänk på vilka mått som måste anges för de olika typerna och undvik åäö.

Vi kan nu använda klasserna för att skapa objekt och utföra beräkningar med dessa. Till exempel ta reda på sammanlagda volymen av en kub med sidan 8 och ett klot med radien 4. I huvudprogrammet skriver vi:

kuben=Kub(8)
klotet=Klot(4)
print(kuben.volym()+klotet.volym())

Uppgift 2.7
Bestäm sammanlagda arean av dessa tre objekt:
En pyramid med bas-sidan 5 och höjden 10
Ett rätblock med längden 8, bredden 4 och höjden 6
En cylinder med radien 5 och höjden 6

En metod som ändrar objektet

Ovanstående objekt kan inte förändras efter att de skapats. Vi kan göra de mer användbara genom att införa metoder som ändrar objektet. Till exempel ändrar dess storlek genom skalning. Följande version av klassen Ratblock kan skalas upp eller ned med metoden skala(k) där k är skalfaktorn:

class Ratblock :
    
    def __init__(self, langd, bredd, hojd):
        self.langd=langd
        self.bredd=bredd
        self.hojd=hojd
    
    def skala(self, k):
        self.langd=self.langd*k
        self.bredd=self.bredd*k
        self.hojd=self.hojd*k
    
    def volym(self):
        return self.langd*self.bredd*self.hojd
    
    def area(self):
        return self.langd*self.hojd*2+self.bredd*self.hojd*2+self.bredd*self.langd*2

Övning 2.23
Lägg till skalningsmetoder för övriga klasser.

Övning 2.24
Hur mycket ändrar sig begräsningsarea och volym på en kub med sidan 4 som skalas upp 10 ggr.