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

Storseminarium 5 - Experimentera

Innan seminariet ska du ha gått igenom Inför seminariet nedan och gjort tillhörande quiz. Syftet med detta är att du ska bekanta dig med innehållet så eventuella frågor kan redas ut under seminariet.

Denna sida visar en del av det som kommer att diskuteras på seminariet. Det kan hända att handledarna också tar upp andra uppgifter som inte behöver något specifikt studiematerial och då syns dessa uppgifter inte på sidan.

Inför seminariet

Under seminariet

Ni kommer att arbeta med uppgifterna tillsammans i mindre grupper, där ni diskuterar och förklarar för varandra, handledarna kommer att finnas till hjälp som under vanligt labb pass. Efter varje uppgift går handledarna igenom sin lösning i helklass, då finns möjlighet att ställa frågor och presentera alternativa lösningar.

Övning 1: Skapa ett fönster med ett textfält och en knapp

Som en första övning i Tkinter ska ni skapa ett enkelt fönster med ett textfält och en knapp. Knappen behöver inte ha någon funktionalitet än, det räcker att den visas i fönstret.

import tkinter as tk

root = tk.Tk()
    
entry = tk.Entry(root)
entry.pack()

button = tk.Button(root, text="Submit")
button.pack()

root.mainloop()

Övning 2: Skriv ut textfältets innehåll när knappen trycks

Nu ska ni lägga till funktionalitet så att när knappen trycks så skrivs innehållet i textfältet ut i terminalen.

import tkinter as tk

root = tk.Tk()
    
entry = tk.Entry(root)
entry.pack()

button = tk.Button(root, text="Submit", command=lambda: print(entry.get()))
button.pack()

root.mainloop()

Övning 3: Uppdatera titelraden med textfältets innehåll

Nu när ni kan hämta textfältets innehåll när knappen trycks, ska ni nu uppdatera fönstrets titelrad med textfältets innehåll istället för att skriva ut det i terminalen.

import tkinter as tk

root = tk.Tk()
    
entry = tk.Entry(root)
entry.pack()

button = tk.Button(root, text="Submit", command=lambda: root.title(entry.get()))
button.pack()

root.mainloop()

Övning 4: Uppdatera automatiskt när textfältet ändras

Nu ska ni ta bort knappen och istället uppdatera titelraden automatiskt varje gång textfältets innehåll ändras. Använd händelsen <KeyRelease> för att uppnå detta.

import tkinter as tk

root = tk.Tk()

def entry_key_release(event):
    root.title(entry.get())
    
entry = tk.Entry(root)
entry.bind("<KeyRelease>", entry_key_release)
entry.pack()

root.mainloop()

Övning 5: Miniräknare

Konceptskiss för miniräknaren.
Konceptskiss för miniräknaren.

Att skapa en miniräknare är en klassisk övning när man lär sig grafiska gränssnitt. I denna övning ska vi skapa en enkel miniräknare med Tkinter som kan utföra grundläggande aritmetiska operationer. Syftet här är inte att skapa en fullfjädrad miniräknare med alla funktioner, utan att använda miniräknaren som ett exempel för att lära sig grunderna i Tkinter och händelsehantering.

Vi ska nu steg för steg skapa en enkel miniräknare med Tkinter som kan utföra addition, subtraktion, multiplikation och division. Miniräknaren ska ha ett inmatningsfält och knappar för siffrorna 0-9, decimalpunkt, samt en knapp för att negera värdet i inmatningsfältet. Utöver det ska det finnas knappar för de fyra räknesätten och en knapp för att rensa inmatningsfältet (CE), en knapp för att rensa allt (CA) och en lika-med-knapp för att beräkna värdet av uttrycket i inmatningsfältet. Se skissen till höger för att se hur miniräknarens layout ska se ut.

Om det visar sig att miniräknaren blir väldigt liten på din skärm kan du justera storleken på det mesta genom att ändra på typsnittsstorleken som används i Tkinter-applikationen. Detta görs genom att skapa en tkinter.font.Font-instans med önskad storlek och sedan använda den i de widgets som skapas. Exempelvis:

1
2
3
4
import tkinter.font as tkFont

default_font = tkFont.nametofont("TkDefaultFont")
default_font.configure(size=14)  # Sets the global font size to 14

Knappar kommer automatiskt att använda typsnittet satt som "TkDefaultFont" och anpassa sin storlek efter texten de innehåller, så genom att ändra typsnittsstorleken ändrar du också knappstorleken. Textfältet använder inte automatiskt detta typsnitt men du kan du kan tvinga det genom att sätta font-parametern när du skapar Entry-widgeten, t.ex. entry = tk.Entry(root, font=default_font).

Övning 5.0: Knappbyggare

Vi kommer att behöva skapa många knappar för miniräknaren. De kommer att dela flera egenskaper, så det är en bra idé att skapa en hjälpfunktion som skapar knappar åt oss. Skapa en funktion create_button som tar följande argument:

  • text: Texten som ska visas på knappen.
  • row: Raden där knappen ska placeras i rutnätet.
  • column: Kolumnen där knappen ska placeras i rutnätet.
  • command: Callback-funktionen som ska anropas när knappen klickas. Defaultvärde är None (vi kommer att lägga till riktiga callbacks längre fram).
  • parent: Föräldra-widgeten där knappen ska placeras. Defaultvärde är det Tk-objekt som är huvudfönster för miniräknaren. (Vi behöver alltså skapa ett sådant objekt först.)

Funktionen ska skapa en Button-widget med de givna egenskaperna och placera den i ett rutnät med hjälp av grid-geometrihanteraren. Om command-argumentet är None, ska knappen skapas utan någon callback-funktion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import tkinter as tk
import tkinter.font as tkFont

calculator = tk.Tk()
calculator.title("Simple Calculator")

# Set
default_font = tkFont.nametofont("TkDefaultFont")
default_font.configure(size=14)  # Sets the global font size to 14

# Create input and output area
data_area = tk.Entry(font=default_font, justify="right")
data_area.grid(row=0, column=0, columnspan=4, padx=15, pady=15, sticky="nsew")


def create_button(text, row, column, command=None, parent=calculator):
    if command is None:
        button = tk.Button(parent, text=text)
    else:
        button = tk.Button(parent, text=text, command=command)
    button.grid(row=row, column=column, sticky="nsew", padx=5, pady=5)

Övning 5.1: Grundläggande layout

Skapa de olika widgets som behövs för miniräknaren med hjälp av Tkinter. Använd grid-geometrihanteraren för att placera ut inmatningsfälet och knapparna i ett rutnät som på konceptskissen ovan. Ge alla widgets en padding på 5 pixlar i både x- och y-led för att skapa lite avstånd mellan dem. (Modifiera create_button-funktionen från Övning 5.0 för att lägga till detta automatiskt när knappar skapas.)

Textrutan ska sträcka sig över hela översta raden (dvs. kolumn 0 till 3) och texten i fältet ska vara högerjusterad (parametern justify till Entry-konstruktorn). Knapparna ska placeras enligt konceptskissen ovan.

OBS! Tecknet på backspace-knappen är en pil som pekar åt vänster ('←'). Denna kan skapas i en sträng i Python med Unicode-escape-sekvensen \u2190, dvs. text="\u2190", eller kopieras från det här stycket.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Create buttons for numbers 1-9
for i in range(1, 10):
    create_button(
        str(i),
        row=((i - 1) // 3) + 2,
        column=(i - 1) % 3,
    )

# Create button for negation
create_button(
    "+/-",
    row=5,
    column=0,
)

# Create button for number 0
create_button(
    "0",
    row=5,
    column=1,
)

# Create decimal point button
create_button(
    ".",
    row=5,
    column=2,
)

# Create clear buttons
create_button(
    "CE",
    row=1,
    column=0,
)
create_button(
    "CA",
    row=1,
    column=1,
)

# Create backspace button
create_button(
    "←",
    row=1,
    column=2,
)

# Create arithmetic operation buttons
for row, symbol in enumerate(["/", "*", "-", "+"], start=1):
    create_button(
        symbol,
        row=row,
        column=3,
    )

# Create equals button
create_button(
    "=",
    row=5,
    column=3,
)

calculator.mainloop()

Övning 5.2: Lägg till funktionalitet för sifferknapparna och räknesättsknapparna

Lägg till funktionalitet för sifferknapparna, decimalpunkt och räknesättsknapparna så att de lägger till motsvarande tecken sist i inmatningsfältet när de trycks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def append_to_data_area(value):
    data_area.insert(tk.END, value)

# Create buttons for numbers 1-9
for i in range(1, 10):
    create_button(
        str(i),
        row=((i - 1) // 3) + 2,
        column=(i - 1) % 3,
        command=lambda x=i: append_to_data_area(str(x)),
    )

# Create button for number 0
create_button(
    "0",
    row=5,
    column=1,
    command=lambda: append_to_data_area("0"),
)

# Create decimal point button
create_button(
    ".",
    row=5,
    column=2,
    command=lambda: append_to_data_area("."),
)

# Create arithmetic operation buttons
for row, symbol in enumerate(["/", "*", "-", "+"], start=1):
    create_button(
        symbol,
        row=row,
        column=3,
    command=lambda x=symbol: append_to_data_area(x),
    )

Övning 5.3: Beräkna uttrycket

Lägg till funktionalitet för =-knappen så att den utvärderar uttrycket i inmatningsfältet och visar resultatet. Använd Python-funktionen eval() för att utvärdera uttrycket. Antag att uttrycket är giltigt.

Funktionen eval() kan vara riskabel att använda i verkliga applikationer eftersom den kan köra godtycklig kod. Använd den endast i kontrollerade miljöer, som denna övning, eller där koden som körs inte kommer direkt från användaren. Funktionen tar en sträng som representerar ett pythonuttryck och evaluerar det uttrycket, t.ex. blir resultatet av eval("2 + 2") heltalet 4.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def compute_expression():
    result = eval(data_area.get())
    data_area.delete(0, tk.END)
    data_area.insert(tk.END, str(result))

# Create equals button
create_button(
    "=",
    row=5,
    column=3,
    command=compute_expression,
)

Övning 5.4: Backspace-knapp

Lägg till funktionalitet för backspace-knappen så att den tar bort det sista tecknet i inmatningsfältet när den trycks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def backspace():
    current_text = data_area.get()
    data_area.delete(len(current_text) - 1, tk.END)

# Create backspace button
create_button(
    "←",
    row=1,
    column=2,
    command=backspace,
)

Övning 5.5: +/- knapp

Lägg till funktionalitet för +/--knappen så att den ändrar tecknet på uttrycket i inmatningsfältet när den trycks. Dvs. om uttrycket inleds med - ska det tas bort, annars ska ett - läggas till i början av uttrycket.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def toggle_negation():
    current_text = data_area.get()
    if current_text.startswith("-"):
        data_area.delete(0)
    else:
        data_area.insert(0, "-")

# Create button for negation
create_button(
    "+/-",
    row=5,
    column=0,
    command=toggle_negation,
)

Övning 5.6: Bonus: Förenkling som försvårar

En traditionell miniräknare har inte möjlighet att skriva in hela uttryck direkt, istället skrivs ett tal in, sedan trycks en räknesättsknapp, sedan skrivs nästa tal in, och så vidare. När lika-med-knappen trycks utförs den sista operationen med de två senaste talen.

Modifiera miniräknaren så att den fungerar på detta sätt. Dvs. när en räknesättsknapp trycks ska det nuvarande värdet i inmatningsfältet sparas och inmatningsfältet rensas, när nästa tal skrivs in och lika-med-knappen trycks ska den sparade operationen utföras med de två talen och resultatet visas.

Var lägger man bäst undan det sparade värdet och den sparade operationen i programmet? Hur ska callback-funktionerna för räknesättsknapparna och lika-med-knappen se ut nu?

Övning 5.7: Bonus: Rensa-knappar

Lägg till funktionalitet för CE- och CA-knapparna så att de rensar inmatningsfältet (CE - Clear Entry) respektive återställer hela miniräknaren till startläge (CA - Clear All).


Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2025-11-09