Grafik Python levereras med ett grafikpaket tkinter De flesta av dagens applikationsprogram hanterar grafik Grafikhantering är komplicerat så använd färdigutvecklade grafikpaket Mycket att hålla reda på Hantera fönster Hantera menyer Hantera omgivningar till fönster och mycket mycket mer Tkinter är ganska enkelt så man bör hitta ett grafikpaket som motsvarar ens egna förväntningar Man bör också titta på vad paketet duger till och vilka plattformar som stödjs Vissa paket tillhandahåller bara grafiska primitiver, s.k. widgets medan andra tillhandahåller fönsterhantering, ritytor, knappar m.m. Så man kan kanske behöva två eller fler paket och måste då välja paket som passar ihop Vi ska testa några enkla saker som öppna ett fönster, lägga till lite knappar, skrivytor för utskrift och inmatning och koppla ihop alltihop. DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 1 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 2 / 18 Starta ett fönster Det allra första blir att få kontakt med grafikpaketet och eftersom man bytt namn på ett lustigt sätt så får man vara lite klurig för att det ska fungera i alla versioner av Python: import tkinter ## bra för versioner >= 3.0 import Tkinter ## för alla andra versioner Sedan skall vi få igång ett fönster: # Vi kör 3.1+ w = g.label(root, text="hallå där!\nköp blåbär!") w.pack() Starta ett fönster... Ofta ser man rekommendationen att importera grafikbiblioteket med from tkinter import * men jag tycker det är bra att alltid ha för vana att importera med ett alias eller prefix, g i Det enda sättet att rita på skärmen är via widgets och vi behöver en fönster-widget, det absolut första vi måste skapa. Fönstret blir ett helt vanligt fönster med alla dekorationer och funktioner så som andra fönster skapas i det OS man använder. Man behöver ett och endast ett sådant rot -fönster. Det här initierar hela grafiksystemet: DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 3 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 4 / 18
Starta ett fönster... Sedan skapar vi en widget för att hålla i texten vi vill presentera, noterar att det skall vara barn till rot-widgeten och säger till rot-widgeten att fönstret ska krympa till textens storlek w = g.label(root, text="hallå där!\nköp blåbär!") w.pack() En Label -widget kan hålla i text, en ikon eller en bild av något slag och man måste tala om vad man vill presentera på skärmen. Funktionen pack krymper fönstret till textens storlek. Innan något händer överhuvudtaget måste vi starta händelsehanteringen Programmet kommer att initiera grafiken, starta fönstret, skapa alla widgets och sedan ligga i en evighetsslinga och ta hand om alla händelser som har med programmet att göra. Testa att flytta förnstret, ändra dess storlek m.m. Stänger vi fönstret kommer händelsehanteraren inse att allt är förbi och avsluta programmet. Hur håller tkinter reda på musklick? Alla händelser i datorn filtreras och det som har med ett speciellt fönster att göra skickas till det fönstret: def klick(h): print ("Klick!", h.x, h.y, h.x_root, h.y_root, h.widget, h.type, h.num) frame = g.frame(root, width=200, height=200) frame.bind("<button-1>", klick) frame.bind("<button-2>", klick) frame.bind("<button-3>", klick) frame.pack() DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 5 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 6 / 18 Olika sätt att organisera fönsterinnehåll Vi har tillgång till Button En enkel knapp för att utföra någonting Canvas Används för att rita eller göra grafiska editorer Checkbutton Ger en box som man kan markera på eller av Varje klick ändrar boxens tillstånd Entry Ett fält för att mata in eller skriva text Frame En slags container för andra widgets Label För att presentera text eller bild Listbox Visar en lista med alternativ där man kan välja ett eller flera alternativ beroende på hur den initieras Menu För pop-up - eller pull-down -menyer Menubutton En menyknapp i en pull-down -meny Message Mer avancerad än Label. Kan ges en bredd för automatisk radbrytning Radiobutton Välj en av flera värden genom att klick på värdet Scale Ger en slide-bar så man kan dra fram till rätt värde Scrollbar Fönsterhiss Text För formaterad text, som kan editeras. Också för inbäddade bilder och fönster Toplevel En container som presenteras som ett eget fönster DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 7 / 18 Och för att placera våra widgets grid En tabelliknande struktur pack En struktur som låter oss packa ihop widgets lite huller om buller place En metod för att placera varje widget exakt och eventuellt oberoende av andra widgets En grid-layout är mest flexibel eftersom de flesta av de widgets man kan använda är rektangulära. Man kan bygga vilka srukturer som helst eftersom en widget kan spänna över mer än en rad eller kolumn. Pack-layout packar i rader eller kolumner. Använd inte tillsammans med grid eftersom tkinter försöker anpassa båda så gott det går. Tar tid, kanske en evighet... Place-layouten är enklast. Man kan placera en widget var som helst i ett fönster antingen i absoluta mått eller relativt någon annan widget. DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 8 / 18
Enkel utbyggnad av tidigare (enkla) exempel def say_hi(): entry.delete(0, g.end) entry.insert(g.insert, "Hallå där! Köp blåbär!") root.title("tk-test") entry = g.entry(root,width=22,bg="yellow") entry.grid(row=0,column=0,columnspan=2, sticky=g.w+g.e+g.n+g.s) button = g.button(root,text="sluta", fg="red",command=root.quit) button.grid(row=1,column=0,sticky=g.w+g.e+g.n+g.s) hi_there = g.button(root, text="hallå", command=say_hi) hi_there.grid(row=1,column=1,sticky=g.w+g.e+g.n+g.s) Mera utbyggnad, irriterande dialog... import tkinter.messagebox as msg def say_hi(): entry.delete(0, g.end) entry.insert(g.insert, "Hallå där! Köp blåbär!") def do_quit(): if msg.askyesno(title= Verkligen sluta?, message= Ska du verkligen sluta? ): root.quit() root.title("tk-test") entry = g.entry(root,width=18,bg="yellow") entry.grid(row=0,column=0,columnspan=2,sticky=g.w+g.e+g.n+g.s) button = g.button(root,text="sluta",fg="red",command=do_quit) button.grid(row=1,column=0,sticky=g.w+g.e+g.n+g.s) hi_there = g.button(root,text="hallå",command=say_hi) hi_there.grid(row=1,column=1,sticky=g.w+g.e+g.n+g.s) DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 9 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 10 / 18 Lite irriterande att alla aktivitets-funktioner inte kan ta parametrar. Om vi bygger en liten kalkylator där man kan använda knappar för att mata in ett uttryck för beräkning blir det många knappar och många funktioner För varje knapp: def klick_1(): klick_key( 1 ) btn_1=tk.button(root,text="1",width=5,relief= ridge,command=klick_1) btn_1.grid(row=3,column=0) Om man har en funktion: def klick_key(key): entry.insert(tk.end, key) Resten blir inte så krångligt och inte så mycket kod def klick_eq(): if / in entry.get() and. not in entry.get(): entry.insert(tk.end, ".0") # guard against the bad guys abusing eval() str1 = "-+0123456789." if entry.get()[0] not in str1: entry.insert(tk.end, "first char not in " + str1) # here comes the calculation part result = eval(entry.get()) entry.insert(tk.end, " = " + str(result)) entry.insert(tk.end, "--> Error!") def klick_c(): # clear entry DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 11 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 12 / 18
def klick_tom(): memory = entry.get() # extract the result if = in memory: ix = memory.find( = ) memory = memory[ix+2:] root.title( M= + memory) def klick_fromm(): entry.insert(tk.end, memory) def klick_neg(): if entry.get()[0] == - : entry.delete(0) entry.insert(0, - ) except IndexError: pass root = tk.tk() root.title("enkel kalkylator") # Här ska knapparna in entry = tk.entry(root, width=33, bg="yellow") entry.grid(row=0, column=0, columnspan=5) DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 13 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 14 / 18 Förenkla koden med lambda-funktion Man kan samla alla knapptryckningar i en funktion (Knyckt från webben och omhackat för python3) import tkinter as tk def click(key): if key == = : # Skydda mot kodinjektion str1 = "-+0123456789." if entry.get()[0] not in str1: entry.insert(tk.end, "Första tecknet inte en av " + str1) # Här börjar beräkningarna result = eval(entry.get()) entry.insert(tk.end, " = " + str(result)) entry.insert(tk.end, "--> Fel!") elif key == C : # Töm displayen Förenkla koden med lambda-funktion... elif key == ->M : memory = entry.get() # Hämta resultatet if = in memory: ix = memory.find( = ) memory = memory[ix+2:] root.title( M= + memory) elif key == M-> : entry.insert(tk.end, memory) elif key == neg : if entry.get()[0] == - : entry.delete(0) entry.insert(0, - ) except IndexError: pass DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 15 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 16 / 18
Förenkla koden med lambda-funktion... Förenkla koden med lambda-funktion... # Föregående beräkning klar, töm diplayen entry.insert(tk.end, key) root = tk.tk() root.title("enkel kalkylator") btn_list = [ 7, 8, 9, *, C, 4, 5, 6, /, M->, 1, 2, 3, -, ->M, 0,., =, +, neg ] # Skapa alla knappar i en enda loop i = 5 for b in btn_list: r, c = divmod(i, 5) rel = ridge cmd = lambda x=b: click(x) tk.button(root,text=b,width=5,relief=rel,command=cmd)\.grid(row=r,column=c) i += 1 # använd en Entry widget som editerbar display entry = tk.entry(root, width=33, bg="yellow") entry.grid(row=0, column=0, columnspan=5) DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 17 / 18 DA2001 (Föreläsning 19) Datalogi 1 Hösten 2011 18 / 18