Tkinter รจ la libreria standard di Python per creare interfacce grafiche (GUI). ร inclusa automaticamente in Python, quindi non devi installare nulla di extra. Il nome viene da "Tk Interface" โ รจ un wrapper attorno al toolkit grafico Tk.
Alla fine del corso avrai costruito una Calcolatrice completa con interfaccia grafica, gestione degli errori e tastiera virtuale.
Tkinter รจ giร incluso in Python. Per verificare che tutto funzioni, apri il terminale e digita:
# Verifica installazione Python python --version # Verifica che Tkinter sia disponibile python -m tkinter
Se si apre una piccola finestra di test, Tkinter รจ pronto! Se hai errori su Linux, installa il pacchetto:
sudo apt-get install python3-tk
Ogni programma Tkinter ha questa struttura fondamentale:
import tkinter as tk # 1. Importa Tkinter root = tk.Tk() # 2. Crea la finestra principale # 3. Aggiungi qui i tuoi widget... root.mainloop() # 4. Avvia il ciclo degli eventi
mainloop() รจ fondamentale: tiene la finestra aperta e ascolta gli eventi (click, tasti premuti,
ecc.). Senza di esso la finestra si chiuderebbe immediatamente.
import tkinter as tktk.Tk()root.mainloop()A cosa serve mainloop()?
Quale delle seguenti รจ la struttura corretta di un programma Tkinter minimo?
tk.Tk() โ mainloop() โ import tkinterimport tkinter โ tk.Tk() โ widget โ mainloop()tk.Tk() โ import tkinter โ mainloop()mainloop() โ import tkinter โ tk.Tk()La finestra principale (root) รจ il contenitore di tutta la tua applicazione. Puoi personalizzarla in molti modi:
import tkinter as tk root = tk.Tk() # Titolo della finestra root.title("La mia prima app") # Dimensioni: larghezza x altezza root.geometry("500x350") # Posizione: 500x350 partendo da (100, 100) root.geometry("500x350+100+100") # Colore di sfondo root.configure(bg="#f0f0f0") # Impedisci il ridimensionamento root.resizable(False, False) # Dimensione minima root.minsize(300, 200) root.mainloop()
Una Label mostra del testo (o un'immagine) nella finestra:
import tkinter as tk root = tk.Tk() root.title("Hello World!") root.geometry("400x200") # Crea una Label label = tk.Label( root, # contenitore padre text="Ciao, Tkinter! ๐", font=("Arial", 20, "bold"), # (font, dimensione, stile) fg="#2563eb", # colore testo (foreground) bg="#eff6ff" # colore sfondo ) # Mostra la label nella finestra label.pack(pady=50) root.mainloop()
text โ il testo da visualizzarefont โ tupla: (famiglia, dimensione, stile)fg / foreground โ colore del testobg / background โ colore dello sfondowidth / height โ dimensioni in caratteri/pixelpadx / pady โ padding interno orizzontale/verticalepack,
grid, o place), altrimenti non apparirร . Vedremo questi in dettaglio nella lezione
4.
Come si imposta il colore del testo di una Label?
color="rosso"fg="#ff0000"text_color="red"
background="red"
I widget sono gli elementi visivi dell'interfaccia: bottoni, caselle di testo, checkbox, ecc. Ecco i piรน importanti:
import tkinter as tk def saluta(): print("Bottone cliccato!") root = tk.Tk() root.geometry("300x200") btn = tk.Button( root, text="Clicca qui!", command=saluta, # funzione da chiamare al click bg="#2563eb", fg="white", font=("Arial", 12), padx=20, pady=8, relief="flat", # stile bordo: flat, raised, sunken cursor="hand2" # cursore al passaggio del mouse ) btn.pack(pady=60) root.mainloop()
import tkinter as tk root = tk.Tk() # Crea l'Entry entry = tk.Entry(root, width=30, font=("Arial", 14)) entry.pack(pady=20) # Testo placeholder manuale entry.insert(0, "Scrivi qualcosa...") def leggi_testo(): testo = entry.get() # legge il valore print(f"Hai scritto: {testo}") def svuota(): entry.delete(0, tk.END) # cancella tutto tk.Button(root, text="Leggi", command=leggi_testo).pack() tk.Button(root, text="Svuota", command=svuota).pack() root.mainloop()
import tkinter as tk root = tk.Tk() # Checkbutton โ variabile collegata BooleanVar var_check = tk.BooleanVar() check = tk.Checkbutton(root, text="Accetta termini", variable=var_check) check.pack() # Radiobutton โ variabile collegata StringVar var_radio = tk.StringVar(value="python") tk.Radiobutton(root, text="Python", variable=var_radio, value="python").pack() tk.Radiobutton(root, text="Java", variable=var_radio, value="java").pack() tk.Radiobutton(root, text="C++", variable=var_radio, value="cpp").pack() def mostra(): print(var_check.get(), var_radio.get()) tk.Button(root, text="Mostra selezione", command=mostra).pack(pady=10) root.mainloop()
Come si legge il valore inserito in un
Entry?
entry.value()entry.textentry.get()entry.read()A differenza di Entry (una riga), il widget Text permette di inserire e
visualizzare testo su piรน righe. ร utile per editor, note, log di output:
import tkinter as tk root = tk.Tk() # Crea il Text (larghezza in caratteri, altezza in righe) txt = tk.Text(root, width=40, height=8, font=("Consolas", 12)) txt.pack(padx=10, pady=10) # Inserisci testo da codice txt.insert(tk.END, "Prima riga\nSeconda riga\n") # Leggi tutto il contenuto contenuto = txt.get("1.0", tk.END) # "1.0" = riga 1, colonna 0 # Leggi una riga specifica riga2 = txt.get("2.0", "2.end") # Cancella tutto txt.delete("1.0", tk.END) # Rendi il testo di sola lettura txt.config(state=tk.DISABLED) root.mainloop()
La Listbox mostra una lista di elementi selezionabili. Spesso viene abbinata a una
Scrollbar per liste lunghe:
import tkinter as tk root = tk.Tk() # Frame con listbox + scrollbar frame = tk.Frame(root) frame.pack(padx=10, pady=10) scrollbar = tk.Scrollbar(frame, orient="vertical") lb = tk.Listbox(frame, height=6, yscrollcommand=scrollbar.set, selectmode=tk.SINGLE) # o tk.MULTIPLE scrollbar.config(command=lb.yview) lb.pack(side="left") scrollbar.pack(side="right", fill="y") # Aggiungi elementi linguaggi = ["Python", "Java", "C++", "JavaScript", "Go", "Rust"] for lang in linguaggi: lb.insert(tk.END, lang) def leggi_selezione(): idx = lb.curselection() # tupla degli indici selezionati if idx: print(lb.get(idx[0])) # testo dell'elemento selezionato tk.Button(root, text="Mostra selezionato", command=leggi_selezione).pack(pady=6) root.mainloop()
La Combobox รจ un campo di testo combinato con un menรน a tendina. Permette all'utente di scegliere da una lista predefinita oppure di digitare liberamente. ร uno dei widget piรน usati nei form di inserimento dati.
import tkinter as tk from tkinter import ttk root = tk.Tk() root.title("Combobox") # โโ Combobox base โโ # values = lista delle voci nel menรน a tendina regioni = ["Lombardia", "Lazio", "Veneto", "Campania", "Toscana", "Sicilia"] ttk.Label(root, text="Seleziona regione:").pack(pady=5) combo = ttk.Combobox(root, values=regioni, width=20) combo.set("Scegli...") # testo iniziale combo.pack(pady=5) # โโ Combobox in sola lettura (non modificabile) โโ combo_ro = ttk.Combobox(root, values=["Python", "JavaScript", "C#", "Java"], state="readonly", # l'utente puรฒ SOLO scegliere dalla lista width=20 ) combo_ro.current(0) # seleziona voce all'indice 0 combo_ro.pack(pady=5) # โโ Leggere il valore selezionato โโ def on_select(event): valore = combo.get() indice = combo.current() # indice nella lista (-1 se digitato) print(f"Selezionato: {valore} (indice {indice})") # evento <<ComboboxSelected>> scatta quando si sceglie una voce combo.bind("<<ComboboxSelected>>", on_select) # โโ Aggiornare la lista dinamicamente โโ def aggiungi_voce(): combo["values"] = list(combo["values"]) + ["Sardegna"] ttk.Button(root, text="Aggiungi Sardegna", command=aggiungi_voce).pack(pady=5) root.mainloop()
Lo Spinbox permette di selezionare un valore numerico (o da una lista) usando le frecce su/giรน. ร ideale per etร , quantitร , anni โ quando il valore deve restare in un intervallo preciso.
import tkinter as tk from tkinter import ttk root = tk.Tk() # โโ Spinbox numerico (from_, to, increment) โโ # from_ e to definiscono il range; increment il passo ttk.Label(root, text="Etร (1โ120):").pack(pady=4) spin_eta = ttk.Spinbox(root, from_=1, to=120, increment=1, width=10, wrap=True # torna a 1 dopo 120 ) spin_eta.set(18) # valore iniziale spin_eta.pack(pady=4) # โโ Spinbox con decimali โโ ttk.Label(root, text="Prezzo (โฌ):").pack(pady=4) spin_prezzo = ttk.Spinbox(root, from_=0.0, to=9999.99, increment=0.5, format="%.2f", # mostra sempre 2 decimali width=10 ) spin_prezzo.set("9.99") spin_prezzo.pack(pady=4) # โโ Spinbox da lista di testi โโ ttk.Label(root, text="Mese:").pack(pady=4) mesi = ("Gennaio","Febbraio","Marzo","Aprile", "Maggio","Giugno","Luglio","Agosto", "Settembre","Ottobre","Novembre","Dicembre") spin_mese = ttk.Spinbox(root, values=mesi, # lista di stringhe anzichรฉ range numerico state="readonly", width=12, wrap=True ) spin_mese.pack(pady=4) # โโ Leggere il valore โโ def leggi(): print(f"Etร : {spin_eta.get()}, Mese: {spin_mese.get()}") ttk.Button(root, text="Leggi valori", command=leggi).pack(pady=8) root.mainloop()
Lo Scale รจ uno slider grafico: l'utente trascina un cursore per scegliere un valore in un intervallo. Utile per volume, luminositร , opacitร .
import tkinter as tk from tkinter import ttk root = tk.Tk() # โโ Scale orizzontale โโ # command viene chiamata ad ogni spostamento del cursore volume_var = tk.IntVar(value=50) lbl_volume = ttk.Label(root, text="Volume: 50") lbl_volume.pack(pady=4) def aggiorna_volume(val): lbl_volume.config(text=f"Volume: {int(float(val))}") scale_v = ttk.Scale(root, from_=0, to=100, orient="horizontal", # o "vertical" variable=volume_var, command=aggiorna_volume, length=300 ) scale_v.pack(pady=4) # โโ Scale verticale con float โโ opacita_var = tk.DoubleVar(value=1.0) ttk.Scale(root, from_=0.0, to=1.0, orient="vertical", variable=opacita_var, length=150 ).pack(pady=4) # โโ Leggere il valore corrente โโ print(f"Volume: {volume_var.get()}") print(f"Opacitร : {opacita_var.get():.2f}") root.mainloop()
La Progressbar mostra visivamente il progresso di un'operazione. Puรฒ essere determinata (con percentuale precisa) o indeterminata (animazione infinita quando non conosci la durata).
import tkinter as tk from tkinter import ttk root = tk.Tk() # โโ Progressbar determinata (valore noto) โโ progress_var = tk.IntVar(value=0) pb = ttk.Progressbar(root, variable=progress_var, maximum=100, mode="determinate", # valore esplicito length=300 ) pb.pack(pady=10) lbl_pct = ttk.Label(root, text="0%") lbl_pct.pack() def avanza(): val = progress_var.get() if val < 100: progress_var.set(val + 10) lbl_pct.config(text=f"{val+10}%") root.after(300, avanza) # richiama se stesso ogni 300ms ttk.Button(root, text="โถ Avvia", command=avanza).pack(pady=4) # โโ Progressbar indeterminata (durata ignota) โโ pb_ind = ttk.Progressbar(root, mode="indeterminate", # animazione perpetua length=300 ) pb_ind.pack(pady=10) def start_stop(): if pb_ind["value"] == 0: pb_ind.start(20) # velocitร in ms else: pb_ind.stop() ttk.Button(root, text="โฏ Start/Stop", command=start_stop).pack(pady=4) root.mainloop()
Il Notebook di ttk crea un'interfaccia a schede (tab), come quelle di un browser. Ogni scheda contiene un Frame indipendente โ perfetto per suddividere form complessi in sezioni.
import tkinter as tk from tkinter import ttk root = tk.Tk() root.geometry("500x350") # โโ Crea il Notebook โโ nb = ttk.Notebook(root) nb.pack(fill="both", expand=True, padx=10, pady=10) # โโ Scheda 1 โ Dati personali โโ # Ogni scheda รจ un Frame normale aggiunto con add() tab1 = ttk.Frame(nb) nb.add(tab1, text=" ๐ค Dati ") # testo del tab ttk.Label(tab1, text="Nome:").grid(row=0, column=0, padx=10, pady=8, sticky="w") ttk.Entry(tab1, width=25).grid(row=0, column=1, padx=10) ttk.Label(tab1, text="Email:").grid(row=1, column=0, padx=10, pady=8, sticky="w") ttk.Entry(tab1, width=25).grid(row=1, column=1, padx=10) # โโ Scheda 2 โ Impostazioni โโ tab2 = ttk.Frame(nb) nb.add(tab2, text=" โ๏ธ Impostazioni ") lingua_var = tk.StringVar(value="it") for txt, val in [("Italiano","it"),("English","en"),("Franรงais","fr")]: ttk.Radiobutton(tab2, text=txt, variable=lingua_var, value=val).pack(anchor="w", padx=20, pady=4) # โโ Scheda 3 โ disabilitata โโ tab3 = ttk.Frame(nb) nb.add(tab3, text=" ๐ Pro ", state="disabled") # โโ Navigare tra tab via codice โโ ttk.Button(root, text="Vai a Impostazioni", command=lambda: nb.select(tab2)).pack(pady=4) # โโ Evento cambio tab โโ def on_tab_change(event): idx = nb.index(nb.select()) # indice tab attivo print(f"Tab attivo: {idx}") nb.bind("<<NotebookTabChanged>>", on_tab_change) root.mainloop()
Il Treeview di ttk รจ il widget piรน potente per visualizzare dati tabulari o strutture ad albero. Ogni riga puรฒ avere sotto-righe, colonne personalizzate, icone e colori alternati.
import tkinter as tk from tkinter import ttk root = tk.Tk() root.geometry("600x400") # โโ Definisci colonne โโ # show="headings" nasconde la colonna albero (#0) cols = ("ID", "Nome", "Ruolo", "Stipendio") tree = ttk.Treeview(root, columns=cols, show="headings", height=12) # โโ Intestazioni e larghezze โโ for col in cols: tree.heading(col, text=col, command=lambda c=col: ordina(c)) tree.column("ID", width=50, anchor="center") tree.column("Nome", width=160, anchor="w") tree.column("Ruolo", width=130, anchor="w") tree.column("Stipendio",width=100, anchor="e") # โโ Inserisci dati โโ dipendenti = [ (1, "Marco Rossi", "Developer", "โฌ42.000"), (2, "Anna Bianchi", "Designer", "โฌ38.000"), (3, "Luigi Verdi", "Team Lead", "โฌ55.000"), (4, "Sara Neri", "QA Engineer", "โฌ35.000"), ] for i, row in enumerate(dipendenti): tag = "pari" if i % 2 == 0 else "dispari" tree.insert("", tk.END, values=row, tags=(tag,)) # โโ Righe alternate colorate โโ tree.tag_configure("pari", background="#f8fafc") tree.tag_configure("dispari", background="#ffffff") # โโ Scrollbar verticale โโ vsb = ttk.Scrollbar(root, orient="vertical", command=tree.yview) tree.configure(yscrollcommand=vsb.set) tree.pack(side="left", fill="both", expand=True) vsb.pack(side="right", fill="y") # โโ Leggere riga selezionata โโ def on_select(event): sel = tree.selection() # lista di item ID if sel: vals = tree.item(sel[0], "values") # dati della riga print(f"Selezionato: {vals}") tree.bind("<<TreeviewSelect>>", on_select) # โโ Ordinamento colonna โโ ordine_rev = {} def ordina(col): dati = [(tree.set(k, col), k) for k in tree.get_children("")] rev = ordine_rev.get(col, False) dati.sort(reverse=rev) for idx, (_, k) in enumerate(dati): tree.move(k, "", idx) ordine_rev[col] = not rev root.mainloop()
Python include il modulo datetime per lavorare con date e ore. In Tkinter non c'รจ un DatePicker
nativo, ma si puรฒ costruirne uno con Spinbox o usare la libreria tkcalendar.
import tkinter as tk from tkinter import ttk from datetime import datetime, date, timedelta root = tk.Tk() root.title("Data e Ora") # โโ Mostrare data/ora corrente โโ ora_var = tk.StringVar() def aggiorna_orologio(): adesso = datetime.now().strftime("%H:%M:%S โ %d/%m/%Y") ora_var.set(adesso) root.after(1000, aggiorna_orologio) # aggiorna ogni secondo ttk.Label(root, textvariable=ora_var, font=("Consolas", 18, "bold")).pack(pady=20) aggiorna_orologio() # โโ Selettore data con Spinbox โโ frame = ttk.LabelFrame(root, text=" Seleziona data ") frame.pack(padx=20, pady=10) oggi = date.today() spin_g = ttk.Spinbox(frame, from_=1, to=31, width=4); spin_g.set(oggi.day) spin_m = ttk.Spinbox(frame, from_=1, to=12, width=4); spin_m.set(oggi.month) spin_a = ttk.Spinbox(frame, from_=2000, to=2100, width=6); spin_a.set(oggi.year) spin_g.grid(row=0, column=0, padx=4, pady=8) ttk.Label(frame, text="/").grid(row=0, column=1) spin_m.grid(row=0, column=2, padx=4) ttk.Label(frame, text="/").grid(row=0, column=3) spin_a.grid(row=0, column=4, padx=4) def calcola_eta(): try: nascita = date(int(spin_a.get()), int(spin_m.get()), int(spin_g.get())) eta = (date.today() - nascita).days // 365 lbl_res.config(text=f"Etร : {eta} anni") except ValueError: lbl_res.config(text="Data non valida") ttk.Button(root, text="Calcola etร ", command=calcola_eta).pack(pady=6) lbl_res = ttk.Label(root, text="", font=("Arial", 12)) lbl_res.pack() # โโ Operazioni con date โโ domani = oggi + timedelta(days=1) settimana = oggi + timedelta(weeks=1) print(f"Oggi: {oggi.strftime('%d/%m/%Y')}") print(f"Domani: {domani.strftime('%d/%m/%Y')}") print(f"Tra 1 settimana: {settimana.strftime('%d/%m/%Y')}") # โโ tkcalendar (se installato: pip install tkcalendar) โโ try: from tkcalendar import DateEntry ttk.Label(root, text="Picker visivo:").pack(pady=4) cal = DateEntry(root, width=14, locale="it_IT", date_pattern="dd/mm/yyyy") cal.pack(pady=4) except ImportError: ttk.Label(root, text="pip install tkcalendar per il picker visivo", foreground="gray").pack(pady=4) root.mainloop()
Il modulo tkinter.messagebox offre finestre di dialogo predefinite per informazioni, avvisi,
errori e domande. Sono finestre modali โ bloccano l'interazione finchรฉ l'utente non risponde.
import tkinter as tk from tkinter import messagebox, filedialog, simpledialog, colorchooser root = tk.Tk() # โโ messagebox โ finestre di dialogo โโ messagebox.showinfo("Titolo", "Operazione completata!") messagebox.showwarning("Attenzione", "Il file รจ di sola lettura.") messagebox.showerror("Errore", "Impossibile aprire il file.") # Dialogo sรฌ/no โ restituisce True o False risposta = messagebox.askyesno("Conferma", "Vuoi eliminare il file?") if risposta: print("Eliminato!") # Dialogo ok/annulla ok = messagebox.askokcancel("Salva", "Salvare le modifiche?") # โโ simpledialog โ input testo/numero โโ nome = simpledialog.askstring("Input", "Come ti chiami?", initialvalue="Mario") eta = simpledialog.askinteger("Input", "Quanti anni hai?", minvalue=1, maxvalue=120) # โโ filedialog โ selezione file โโ # Apri un singolo file file_path = filedialog.askopenfilename( title="Apri file", filetypes=[("Testo","*.txt"),("Python","*.py"),("Tutti","*.*")] ) # Apri piรน file files = filedialog.askopenfilenames(title="Seleziona immagini", filetypes=[("Immagini","*.png *.jpg *.gif")]) # Salva file save_path = filedialog.asksaveasfilename( defaultextension=".txt", filetypes=[("Testo","*.txt")] ) # Seleziona cartella cartella = filedialog.askdirectory(title="Scegli cartella") # โโ colorchooser โ selettore colore โโ colore = colorchooser.askcolor(color="#1877f2", title="Scegli colore") # restituisce ((R,G,B), "#rrggbb") oppure (None, None) se annullato if colore[1]: print(f"Colore HEX: {colore[1]}") root.mainloop()
Quale widget permette di scegliere tra valori predefiniti oppure digitare un testo libero?
ttk.Listboxttk.Comboboxttk.Spinboxttk.OptionMenuQuale evento scatta quando si seleziona una riga in un Treeview?
<Button-1>
<TreeviewClick>
<<TreeviewSelect>><Select>I Geometry Managers decidono dove e come i widget vengono posizionati. Ce ne sono 3:
Mette i widget uno dopo l'altro (verticalmente o orizzontalmente).
import tkinter as tk root = tk.Tk() # side: TOP (default), BOTTOM, LEFT, RIGHT tk.Label(root, text="In alto", bg="#fca5a5").pack(side="top", fill="x", pady=5) tk.Label(root, text="Sinistra", bg="#86efac").pack(side="left", fill="y", padx=5) tk.Label(root, text="Destra", bg="#93c5fd").pack(side="right", fill="y", padx=5) tk.Label(root, text="Centro", bg="#fde68a").pack(expand=True) # fill: "x", "y", "both", None # expand: True/False โ occupa spazio extra disponibile root.mainloop()
Il piรน potente per form e layout strutturati:
import tkinter as tk root = tk.Tk() root.title("Form con Grid") # Etichette โ colonna 0 tk.Label(root, text="Nome:").grid(row=0, column=0, sticky="e", padx=10, pady=8) tk.Label(root, text="Email:").grid(row=1, column=0, sticky="e", padx=10, pady=8) tk.Label(root, text="Etร :").grid(row=2, column=0, sticky="e", padx=10, pady=8) # Entry โ colonna 1 nome = tk.Entry(root, width=25) email = tk.Entry(root, width=25) eta = tk.Entry(root, width=25) nome.grid(row=0, column=1, pady=8) email.grid(row=1, column=1, pady=8) eta.grid(row=2, column=1, pady=8) # Bottone su 2 colonne tk.Button(root, text="Invia").grid(row=3, column=0, columnspan=2, pady=12) # sticky: "n","s","e","w" o combinazioni "nsew" โ allineamento # columnspan: unisce piรน colonne root.mainloop()
Posiziona i widget con coordinate esatte (x, y). Utile per layout custom ma meno flessibile:
tk.Label(root, text="Precisione assoluta").place(x=100, y=50) # oppure con percentuali tk.Label(root, text="Centrato").place(relx=0.5, rely=0.5, anchor="center")
pack() e grid() nello stesso contenitore padre. Puoi mescolarli in
Frame diversi, ma non nello stesso.
Quale geometry manager รจ piรน adatto per creare un form con etichette e campi allineati?
pack()grid()place()Tkinter usa un sistema ad eventi: quando l'utente interagisce (click, tasto premuto, movimento del mouse), viene generato un evento. Puoi rispondere in due modi:
import tkinter as tk root = tk.Tk() contatore = [0] # lista per mutabilitร in closure lbl = tk.Label(root, text="Clic: 0", font=("Arial", 16)) lbl.pack(pady=20) def incrementa(): contatore[0] += 1 lbl.config(text=f"Clic: {contatore[0]}") tk.Button(root, text="Clicca!", command=incrementa).pack() root.mainloop()
import tkinter as tk root = tk.Tk() lbl = tk.Label(root, text="Interagisci!", font=("Arial", 14)) lbl.pack(pady=30, padx=30) # Evento click sinistro del mouse def al_click(event): lbl.config(text=f"Click su ({event.x}, {event.y})") # Tasto premuto sulla tastiera def al_tasto(event): lbl.config(text=f"Tasto: {event.keysym}") # Mouse sopra il widget def al_hover(event): lbl.config(bg="#dbeafe") def al_leave(event): lbl.config(bg="SystemButtonFace") root.bind("<Button-1>", al_click) # click sinistro root.bind("<Key>", al_tasto) # qualsiasi tasto lbl.bind("<Enter>", al_hover) # mouse entra lbl.bind("<Leave>", al_leave) # mouse esce root.mainloop()
<Button-1> โ click sinistro mouse<Button-3> โ click destro mouse<Double-Button-1> โ doppio click<Key> โ qualsiasi tasto tastiera<Return> โ tasto Invio<Escape> โ tasto Esc<Enter> โ mouse entra nel widget<Leave> โ mouse esce dal widget<Configure> โ finestra ridimensionataSe devi passare parametri alla funzione nel command=, usa lambda:
def cambia_colore(widget, colore): widget.config(bg=colore) # โ Corretto โ usa lambda tk.Button(root, text="Rosso", command=lambda: cambia_colore(label, "red")).pack() # โ Sbagliato โ chiama subito la funzione tk.Button(root, text="Verde", command=cambia_colore(label, "green")).pack()
Quale stringa rappresenta l'evento "tasto Invio premuto"?
<Enter-Key>
<Key-Enter>
<Return><Newline>Tkinter ha variabili speciali che si sincronizzano automaticamente con i widget. Quando cambia la variabile, il widget si aggiorna; quando l'utente modifica il widget, la variabile si aggiorna.
tk.StringVar() โ per testotk.IntVar() โ per interitk.DoubleVar() โ per decimalitk.BooleanVar() โ per True/Falseimport tkinter as tk root = tk.Tk() # StringVar collegata all'Entry nome_var = tk.StringVar() entry = tk.Entry(root, textvariable=nome_var) entry.pack(pady=10) # Label che mostra il valore automaticamente lbl = tk.Label(root, textvariable=nome_var, fg="blue") lbl.pack() # Modificare la variabile da codice def imposta(): nome_var.set("Mario Rossi") # aggiorna entry e label! def leggi(): print(nome_var.get()) # legge il valore tk.Button(root, text="Imposta", command=imposta).pack() tk.Button(root, text="Leggi", command=leggi).pack() root.mainloop()
import tkinter as tk root = tk.Tk() testo = tk.StringVar() # Callback chiamata ogni volta che la var cambia def on_change(*args): val = testo.get() lunghezza.config(text=f"Caratteri: {len(val)}") # "w" = write (quando viene scritto) testo.trace("w", on_change) tk.Entry(root, textvariable=testo, width=30).pack(pady=10) lunghezza = tk.Label(root, text="Caratteri: 0") lunghezza.pack() root.mainloop()
StringVar, IntVar, ecc. quando hai bisogno che un widget si aggiorni
automaticamente al cambiare dei dati, o quando vuoi collegare piรน widget allo stesso valore.
Come si aggiorna il valore di una
StringVar?
var.update("valore")
var.set("valore")
var = "valore"var.write("valore")
import tkinter as tk root = tk.Tk() root.title("Finestra Principale") def apri_finestra(): top = tk.Toplevel(root) # finestra secondaria top.title("Finestra Secondaria") top.geometry("300x150") top.transient(root) # legata alla finestra padre top.grab_set() # blocca la finestra padre (modale) tk.Label(top, text="Ciao dalla finestra secondaria!").pack(pady=20) tk.Button(top, text="Chiudi", command=top.destroy).pack() root.wait_window(top) # aspetta che si chiuda tk.Button(root, text="Apri finestra", command=apri_finestra).pack(pady=50) root.mainloop()
Tkinter include dialog box pronti all'uso:
from tkinter import messagebox, simpledialog, filedialog # Messaggi messagebox.showinfo("Info", "Operazione completata!") messagebox.showwarning("Attenzione", "Controlla i dati") messagebox.showerror("Errore", "Qualcosa รจ andato storto") # Domanda sรฌ/no โ restituisce True/False risposta = messagebox.askyesno("Conferma", "Vuoi continuare?") # Input di testo nome = simpledialog.askstring("Input", "Come ti chiami?") eta = simpledialog.askinteger("Input", "Quanti anni hai?") # Apri file percorso = filedialog.askopenfilename( title="Seleziona file", filetypes=[("File di testo", "*.txt"), ("Tutti", "*.*")] ) # Salva file salva = filedialog.asksaveasfilename(defaultextension=".txt")
import tkinter as tk from tkinter import messagebox root = tk.Tk() # Barra del menu principale menubar = tk.Menu(root) # Menu File file_menu = tk.Menu(menubar, tearoff=0) file_menu.add_command(label="Nuovo", accelerator="Ctrl+N") file_menu.add_command(label="Apri", accelerator="Ctrl+O") file_menu.add_separator() file_menu.add_command(label="Esci", command=root.quit) menubar.add_cascade(label="File", menu=file_menu) # Menu Aiuto help_menu = tk.Menu(menubar, tearoff=0) help_menu.add_command(label="Info", command=lambda: messagebox.showinfo("Info", "v1.0")) menubar.add_cascade(label="Aiuto", menu=help_menu) root.config(menu=menubar) root.mainloop()
Quale widget si usa per creare finestre secondarie in Tkinter?
tk.Window()tk.Toplevel()tk.Frame()tk.Dialog()Costruiremo una calcolatrice grafica completa con: display numerico, tastiera virtuale con tutti gli operatori, gestione degli errori (divisione per zero, ecc.), effetti hover sui tasti, e supporto alla tastiera fisica.
Iniziamo con la struttura dell'applicazione:
import tkinter as tk from tkinter import messagebox class Calcolatrice: def __init__(self, root): self.root = root self.root.title("๐งฎ Calcolatrice") self.root.geometry("320x480") self.root.resizable(False, False) self.root.configure(bg="#1e1e2e") # Stato interno self.espressione = "" self.display_var = tk.StringVar(value="0") self.nuovo_numero = True # dopo un operatore self.crea_display() self.crea_tasti() self.bind_tastiera() # Avvia l'applicazione if __name__ == "__main__": root = tk.Tk() app = Calcolatrice(root) root.mainloop()
def crea_display(self): # Frame display frame_display = tk.Frame(self.root, bg="#1e1e2e") frame_display.pack(fill="x", padx=12, pady=12) # Riga espressione (piccola, grigia) self.lbl_expr = tk.Label( frame_display, text="", font=("Consolas", 12), fg="#6c7086", bg="#1e1e2e", anchor="e" ) self.lbl_expr.pack(fill="x") # Riga risultato (grande) self.lbl_display = tk.Label( frame_display, textvariable=self.display_var, font=("Consolas", 36, "bold"), fg="#cdd6f4", bg="#1e1e2e", anchor="e" ) self.lbl_display.pack(fill="x") # Linea separatrice tk.Frame(self.root, bg="#313244", height=1).pack(fill="x", padx=12)
def crea_tasti(self): # Layout: (testo, riga, colonna, colspan, tipo) tasti = [ ("C", 0, 0, 1, "fn"), ("ยฑ", 0, 1, 1, "fn"), ("%", 0, 2, 1, "op"), ("รท", 0, 3, 1, "op"), ("7", 1, 0, 1, "num"), ("8", 1, 1, 1, "num"), ("9", 1, 2, 1, "num"), ("ร", 1, 3, 1, "op"), ("4", 2, 0, 1, "num"), ("5", 2, 1, 1, "num"), ("6", 2, 2, 1, "num"), ("-", 2, 3, 1, "op"), ("1", 3, 0, 1, "num"), ("2", 3, 1, 1, "num"), ("3", 3, 2, 1, "num"), ("+", 3, 3, 1, "op"), ("0", 4, 0, 2, "num"), (".", 4, 2, 1, "num"), ("=", 4, 3, 1, "eq"), ] colori = { "num": ("#313244", "#45475a"), # normale, hover "op": ("#f38ba8", "#eba0ac"), "fn": ("#45475a", "#585b70"), "eq": ("#a6e3a1", "#94e2d5"), } frame_tasti = tk.Frame(self.root, bg="#1e1e2e") frame_tasti.pack(fill="both", expand=True, padx=12, pady=12) for c in range(4): frame_tasti.columnconfigure(c, weight=1) for r in range(5): frame_tasti.rowconfigure(r, weight=1) for (testo, riga, col, span, tipo) in tasti: bg, hover = colori[tipo] fg = "#1e1e2e" if tipo in ("eq", "op") else "#cdd6f4" btn = tk.Button( frame_tasti, text=testo, font=("Consolas", 18, "bold"), bg=bg, fg=fg, activebackground=hover, relief="flat", bd=0, cursor="hand2", command=lambda t=testo: self.premi(t) ) btn.grid(row=riga, column=col, columnspan=span, sticky="nsew", padx=3, pady=3) # Effetto hover btn.bind("<Enter>", lambda e, b=btn, h=hover: b.config(bg=h)) btn.bind("<Leave>", lambda e, b=btn, n=bg: b.config(bg=n))
def premi(self, tasto): if tasto == "C": self.espressione = "" self.display_var.set("0") self.lbl_expr.config(text="") elif tasto == "ยฑ": try: val = float(self.display_var.get()) self.display_var.set(str(-val)) except: pass elif tasto == "%": try: val = float(self.display_var.get()) self.display_var.set(str(val / 100)) except: pass elif tasto == "=": try: expr = self.espressione + self.display_var.get() # Converti simboli in operatori Python expr = expr.replace("ร", "*").replace("รท", "/") self.lbl_expr.config(text=expr + " =") risultato = eval(expr) # Mostra intero se possibile if risultato == int(risultato): risultato = int(risultato) self.display_var.set(str(risultato)) self.espressione = "" self.nuovo_numero = True except ZeroDivisionError: self.display_var.set("Errore: รท0") self.espressione = "" except: self.display_var.set("Errore") self.espressione = "" elif tasto in ("+", "-", "ร", "รท"): self.espressione += self.display_var.get() + tasto self.lbl_expr.config(text=self.espressione) self.nuovo_numero = True else: # Numero o punto if self.nuovo_numero: self.display_var.set(tasto) self.nuovo_numero = False else: attuale = self.display_var.get() if tasto == "." and "." in attuale: return # non inserire due punti if attuale == "0" and tasto != ".": self.display_var.set(tasto) else: self.display_var.set(attuale + tasto) def bind_tastiera(self): # Supporto tastiera fisica for k in "0123456789.": self.root.bind(f"<Key-{k}>", lambda e, t=k: self.premi(t)) self.root.bind("<Return>", lambda e: self.premi("=")) self.root.bind("<Escape>", lambda e: self.premi("C")) self.root.bind("<plus>", lambda e: self.premi("+")) self.root.bind("<minus>", lambda e: self.premi("-")) self.root.bind("<asterisk>", lambda e: self.premi("ร")) self.root.bind("<slash>", lambda e: self.premi("รท"))
import tkinter as tk class Calcolatrice: def __init__(self, root): self.root = root self.root.title("๐งฎ Calcolatrice") self.root.geometry("320x480") self.root.resizable(False, False) self.root.configure(bg="#1e1e2e") self.espressione = "" self.display_var = tk.StringVar(value="0") self.nuovo_numero = True self.crea_display() self.crea_tasti() self.bind_tastiera() def crea_display(self): f = tk.Frame(self.root, bg="#1e1e2e") f.pack(fill="x", padx=12, pady=12) self.lbl_expr = tk.Label(f, text="", font=("Consolas",12), fg="#6c7086", bg="#1e1e2e", anchor="e") self.lbl_expr.pack(fill="x") tk.Label(f, textvariable=self.display_var, font=("Consolas",36,"bold"), fg="#cdd6f4", bg="#1e1e2e", anchor="e").pack(fill="x") tk.Frame(self.root, bg="#313244", height=1).pack(fill="x", padx=12) def crea_tasti(self): layout = [ ("C",0,0,1,"fn"),( "ยฑ",0,1,1,"fn"),("%",0,2,1,"op"),("รท",0,3,1,"op"), ("7",1,0,1,"num"),("8",1,1,1,"num"),("9",1,2,1,"num"),("ร",1,3,1,"op"), ("4",2,0,1,"num"),("5",2,1,1,"num"),("6",2,2,1,"num"),("-",2,3,1,"op"), ("1",3,0,1,"num"),("2",3,1,1,"num"),("3",3,2,1,"num"),("+",3,3,1,"op"), ("0",4,0,2,"num"),(".",4,2,1,"num"),("=",4,3,1,"eq"), ] C = {"num":("#313244","#45475a"),"op":("#f38ba8","#eba0ac"), "fn":("#45475a","#585b70"), "eq":("#a6e3a1","#94e2d5")} f = tk.Frame(self.root, bg="#1e1e2e") f.pack(fill="both", expand=True, padx=12, pady=12) [f.columnconfigure(i,weight=1) for i in range(4)] [f.rowconfigure(i,weight=1) for i in range(5)] for (t,r,c,s,tipo) in layout: bg,hov = C[tipo] fg = "#1e1e2e" if tipo in ("eq","op") else "#cdd6f4" b = tk.Button(f, text=t, font=("Consolas",18,"bold"), bg=bg,fg=fg,activebackground=hov,relief="flat",bd=0, cursor="hand2",command=lambda x=t: self.premi(x)) b.grid(row=r,column=c,columnspan=s,sticky="nsew",padx=3,pady=3) b.bind("<Enter>",lambda e,b=b,h=hov: b.config(bg=h)) b.bind("<Leave>",lambda e,b=b,n=bg: b.config(bg=n)) def premi(self, t): if t == "C": self.espressione = ""; self.display_var.set("0") self.lbl_expr.config(text=""); self.nuovo_numero = True elif t == "ยฑ": try: self.display_var.set(str(-float(self.display_var.get()))) except: pass elif t == "%": try: self.display_var.set(str(float(self.display_var.get())/100)) except: pass elif t == "=": try: expr = (self.espressione+self.display_var.get()).replace("ร","*").replace("รท","/") self.lbl_expr.config(text=expr+" =") r = eval(expr) self.display_var.set(str(int(r) if r==int(r) else r)) self.espressione = ""; self.nuovo_numero = True except ZeroDivisionError: self.display_var.set("Errore: รท0"); self.espressione = "" except: self.display_var.set("Errore"); self.espressione = "" elif t in "+-รรท": self.espressione += self.display_var.get()+t self.lbl_expr.config(text=self.espressione); self.nuovo_numero = True else: if self.nuovo_numero: self.display_var.set(t); self.nuovo_numero = False else: v = self.display_var.get() if t=="." and "." in v: return self.display_var.set(t if v=="0" and t!="." else v+t) def bind_tastiera(self): for k in "0123456789.": self.root.bind(f"<{k}>", lambda e,t=k: self.premi(t)) self.root.bind("<Return>", lambda e: self.premi("=")) self.root.bind("<Escape>", lambda e: self.premi("C")) self.root.bind("<plus>", lambda e: self.premi("+")) self.root.bind("<minus>", lambda e: self.premi("-")) self.root.bind("<asterisk>", lambda e: self.premi("ร")) self.root.bind("<slash>", lambda e: self.premi("รท")) if __name__ == "__main__": root = tk.Tk() Calcolatrice(root) root.mainloop()
open().
Ottimo lavoro! Hai imparato:
ttk (Themed Tk) รจ un modulo di Tkinter che fornisce widget con un aspetto nativo del sistema operativo e supporto a temi personalizzati. I widget ttk sostituiscono quelli classici con versioni piรน moderne e stilizzate.
tk.Button per controllo totale sui colori. Usa ttk.Button quando vuoi l'aspetto
nativo e i temi. Per stili avanzati, ttk usa Style() invece di opzioni dirette.
import tkinter as tk from tkinter import ttk root = tk.Tk() root.title("Widget ttk") root.geometry("400x300") # ttk.Button โ aspetto nativo ttk.Button(root, text="Bottone ttk").pack(pady=10) # ttk.Entry ttk.Entry(root, width=25).pack(pady=5) # ttk.Combobox โ menรน a tendina combo = ttk.Combobox(root, values=["Python", "Java", "C++"]) combo.set("Seleziona linguaggio") combo.pack(pady=5) # ttk.Progressbar pb = ttk.Progressbar(root, length=200, mode="determinate") pb.pack(pady=10) pb["value"] = 65 # 65% # ttk.Scale โ slider ttk.Scale(root, from_=0, to=100, orient="horizontal").pack(pady=5) # ttk.Notebook โ schede/tab nb = ttk.Notebook(root) tab1 = ttk.Frame(nb) tab2 = ttk.Frame(nb) nb.add(tab1, text="Scheda 1") nb.add(tab2, text="Scheda 2") nb.pack(expand=True, fill="both", pady=10) root.mainloop()
import tkinter as tk from tkinter import ttk root = tk.Tk() root.configure(bg="#1a1a2e") # Crea oggetto Style style = ttk.Style() # Scegli un tema di base print(style.theme_names()) # ('winnative', 'clam', 'alt', 'default', ...) style.theme_use("clam") # tema piรน personalizzabile # Personalizza il Button style.configure( "Accent.TButton", # nome stile personalizzato background="#7c6af7", foreground="white", font=("Syne", 12, "bold"), padding=(12, 6), borderwidth=0 ) style.map("Accent.TButton", background=[("active", "#9b8bff")] # hover ) # Applica lo stile ttk.Button(root, text="Bottone Accent", style="Accent.TButton").pack(pady=30) # Personalizza Label style.configure("Dark.TLabel", background="#1a1a2e", foreground="#cdd6f4", font=("Consolas", 14) ) ttk.Label(root, text="Label scura", style="Dark.TLabel").pack() root.mainloop()
Come si applica uno stile personalizzato a un widget ttk?
ttk.Button(root, bg="#333")ttk.Button(root, style="MioStile.TButton")ttk.Button(root, theme="dark")ttk.Button(root, color="blue")Vuoi mostrare dati tabellari (tipo una rubrica) in Tkinter. Quale widget ttk รจ piรน adatto?
ttk.Comboboxttk.Progressbarttk.Treeviewttk.NotebookIl Canvas รจ una tela su cui puoi disegnare forme geometriche, testo, immagini e persino creare giochi 2D. ร il widget piรน potente di Tkinter per la grafica.
import tkinter as tk root = tk.Tk() root.title("Canvas Demo") # Crea il canvas canvas = tk.Canvas(root, width=500, height=400, bg="#1e1e2e") canvas.pack() # Rettangolo: (x1,y1, x2,y2) canvas.create_rectangle(50, 50, 150, 120, fill="#7c6af7", outline="#9b8bff", width=2) # Ovale/Cerchio canvas.create_oval(200, 50, 320, 170, fill="#a6e3a1", outline="white") # Linea canvas.create_line(50, 200, 450, 200, fill="#f38ba8", width=3, dash=(10, 5)) # Poligono (triangolo) canvas.create_polygon(250, 230, 200, 320, 300, 320, fill="#f7a26a", outline="white", width=2) # Testo sul canvas canvas.create_text(250, 370, text="Canvas Tkinter!", font=("Consolas", 16, "bold"), fill="#cdd6f4") root.mainloop()
Ogni oggetto disegnato ha un ID che puoi usare per modificarlo o eliminarlo:
import tkinter as tk root = tk.Tk() canvas = tk.Canvas(root, width=400, height=300, bg="#1e1e2e") canvas.pack() # L'ID viene restituito alla creazione cerchio = canvas.create_oval(150, 100, 250, 200, fill="#7c6af7") def sposta(): # Sposta di 10px a destra e 5px in basso canvas.move(cerchio, 10, 5) def cambia_colore(): canvas.itemconfig(cerchio, fill="#f38ba8") def elimina(): canvas.delete(cerchio) def pulisci_tutto(): canvas.delete("all") # cancella TUTTO tk.Button(root, text="Sposta", command=sposta).pack(side="left", padx=4) tk.Button(root, text="Cambia colore", command=cambia_colore).pack(side="left", padx=4) tk.Button(root, text="Elimina", command=elimina).pack(side="left", padx=4) root.mainloop()
import tkinter as tk root = tk.Tk() root.title("Mini Paint") colore_attuale = ["#7c6af7"] spessore = [4] prev_x = prev_y = [None] canvas = tk.Canvas(root, width=600, height=400, bg="white", cursor="crosshair") canvas.pack() def disegna(event): if prev_x[0] is not None: canvas.create_line( prev_x[0], prev_y[0], event.x, event.y, fill=colore_attuale[0], width=spessore[0], capstyle=tk.ROUND, smooth=True ) prev_x[0], prev_y[0] = event.x, event.y def reset_prev(event): prev_x[0] = prev_y[0] = None canvas.bind("<B1-Motion>", disegna) canvas.bind("<ButtonRelease-1>", reset_prev) # Barra strumenti colori barra = tk.Frame(root, bg="#f0f0f0") barra.pack(fill="x") colori = ["#7c6af7", "#f38ba8", "#a6e3a1", "#f7a26a", "black", "white"] for c in colori: tk.Button(barra, bg=c, width=3, command=lambda x=c: colore_attuale.__setitem__(0, x)).pack(side="left", padx=2, pady=4) tk.Button(barra, text="๐ Pulisci", command=lambda: canvas.delete("all")).pack(side="right", padx=8) root.mainloop()
Come si cancellano TUTTI gli oggetti da un Canvas?
canvas.clear()canvas.remove_all()
canvas.delete("all")
canvas.destroy()
Per modificare il colore di un oggetto giร disegnato sul Canvas, quale metodo usi?
canvas.config(id, fill="red")canvas.itemconfig(id, fill="red")canvas.update(id, fill="red")canvas.set(id, fill="red")after(ms, funzione) esegue una funzione dopo un certo numero di millisecondi. Chiamandolo
ricorsivamente crei un loop di animazione โ il cuore di qualsiasi animazione in Tkinter.
time.sleep() blocca il mainloop e congela l'intera interfaccia. Usa sempre after()
per i ritardi nelle GUI.
import tkinter as tk root = tk.Tk() root.title("Palla Rimbalzante") canvas = tk.Canvas(root, width=500, height=400, bg="#1e1e2e") canvas.pack() # Posizione e velocitร x, y = 250, 200 dx, dy = 4, 3 r = 25 palla = canvas.create_oval(x-r, y-r, x+r, y+r, fill="#7c6af7", outline="") def anima(): global x, y, dx, dy x += dx y += dy # Rimbalzo sui bordi if x - r <= 0 or x + r >= 500: dx = -dx if y - r <= 0 or y + r >= 400: dy = -dy # Aggiorna posizione canvas.coords(palla, x-r, y-r, x+r, y+r) # Chiama se stessa ogni 16ms (~60fps) root.after(16, anima) anima() # avvia il loop root.mainloop()
import tkinter as tk root = tk.Tk() root.title("Countdown") root.configure(bg="#1e1e2e") secondi = [10] timer_id = [None] lbl = tk.Label(root, text="10", font=("Consolas", 72, "bold"), fg="#a6e3a1", bg="#1e1e2e") lbl.pack(pady=30, padx=60) def tick(): secondi[0] -= 1 lbl.config(text=str(secondi[0])) if secondi[0] <= 3: lbl.config(fg="#f38ba8") # rosso negli ultimi secondi if secondi[0] > 0: timer_id[0] = root.after(1000, tick) else: lbl.config(text="โฐ Fine!", fg="#f7a26a") def avvia(): secondi[0] = 10 lbl.config(text="10", fg="#a6e3a1") tick() def ferma(): if timer_id[0]: root.after_cancel(timer_id[0]) # cancella il prossimo tick tk.Button(root, text="โถ Avvia", command=avvia, bg="#a6e3a1", fg="#1e1e2e", font=("Syne",12,"bold")).pack(side="left", padx=20, pady=10) tk.Button(root, text="โน Ferma", command=ferma, bg="#f38ba8", fg="#1e1e2e", font=("Syne",12,"bold")).pack(side="left", padx=4) root.mainloop()
from tkinter import ttk import tkinter as tk root = tk.Tk() valore = tk.DoubleVar() pb = ttk.Progressbar(root, variable=valore, maximum=100, length=300) pb.pack(pady=30, padx=30) lbl = tk.Label(root, text="0%", font=("Consolas", 14)) lbl.pack() def avanza(): v = valore.get() if v < 100: valore.set(v + 1) lbl.config(text=f"{int(v+1)}%") root.after(30, avanza) else: lbl.config(text="โ Completato!") tk.Button(root, text="Avvia caricamento", command=lambda: [valore.set(0), avanza()]).pack(pady=10) root.mainloop()
Vuoi eseguire una funzione
aggiorna() ogni 500ms. Quale codice รจ corretto?
time.sleep(0.5); aggiorna()root.after(500, aggiorna)root.timer(500, aggiorna)root.loop(aggiorna, 500)Questo codice vuole animare una palla ma la finestra si blocca. Perchรฉ?
import time
def anima():
while True:
canvas.move(palla, 2, 0)
time.sleep(0.016)
canvas.move non
esistetime.sleep() blocca il
mainloop di Tkinterroot.mainloop()
SQLite รจ un database integrato in Python (no installazione), perfetto per app desktop. Combinato con Tkinter puoi creare applicazioni con dati persistenti โ rubrica, gestione studenti, inventario, ecc.
import sqlite3 funziona con qualsiasi Python 3.x.
import sqlite3 # Connessione (crea il file se non esiste) conn = sqlite3.connect("rubrica.db") cur = conn.cursor() # CREATE โ crea la tabella cur.execute(""" CREATE TABLE IF NOT EXISTS contatti ( id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL, email TEXT, tel TEXT ) """) # INSERT โ aggiungi un contatto cur.execute("INSERT INTO contatti (nome, email, tel) VALUES (?, ?, ?)", ("Mario Rossi", "[email protected]", "333-1234567")) # SELECT โ leggi tutti i contatti cur.execute("SELECT * FROM contatti") righe = cur.fetchall() for riga in righe: print(riga) # UPDATE cur.execute("UPDATE contatti SET tel=? WHERE nome=?", ("333-9999999", "Mario Rossi")) # DELETE cur.execute("DELETE FROM contatti WHERE id=?", (1,)) conn.commit() # salva le modifiche conn.close() # chiudi la connessione
import tkinter as tk from tkinter import ttk, messagebox import sqlite3 class RubricaApp: def __init__(self, root): self.root = root self.root.title("๐ Rubrica") self.root.geometry("620x480") self.root.configure(bg="#f8fafc") self.init_db() self.crea_ui() self.carica_contatti() def init_db(self): # Inizializza il database self.conn = sqlite3.connect("rubrica.db") self.cur = self.conn.cursor() self.cur.execute(""" CREATE TABLE IF NOT EXISTS contatti ( id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL, email TEXT, tel TEXT )""") self.conn.commit() def crea_ui(self): # Frame form in alto form = tk.LabelFrame(self.root, text=" Nuovo Contatto ", font=("Arial",10,"bold"), bg="#f8fafc") form.pack(fill="x", padx=16, pady=12) etichette = ["Nome:", "Email:", "Telefono:"] self.campi = {} for i, et in enumerate(etichette): tk.Label(form, text=et, bg="#f8fafc").grid(row=i, column=0, sticky="e", padx=8, pady=5) chiave = et[:-1].lower() e = ttk.Entry(form, width=30) e.grid(row=i, column=1, pady=5, padx=4) self.campi[chiave] = e btn_frame = tk.Frame(form, bg="#f8fafc") btn_frame.grid(row=3, column=0, columnspan=2, pady=8) tk.Button(btn_frame, text="โ Aggiungi", command=self.aggiungi, bg="#7c6af7", fg="white", relief="flat", padx=12).pack(side="left", padx=4) tk.Button(btn_frame, text="๐ Elimina selezionato", command=self.elimina, bg="#f38ba8", fg="white", relief="flat", padx=12).pack(side="left", padx=4) # Tabella contatti (Treeview) cols = ("ID", "Nome", "Email", "Telefono") self.tree = ttk.Treeview(self.root, columns=cols, show="headings", height=12) for c in cols: self.tree.heading(c, text=c) self.tree.column(c, width=60 if c=="ID" else 160) self.tree.pack(fill="both", expand=True, padx=16, pady=4) def carica_contatti(self): for i in self.tree.get_children(): self.tree.delete(i) self.cur.execute("SELECT * FROM contatti ORDER BY nome") for riga in self.cur.fetchall(): self.tree.insert("", tk.END, values=riga) def aggiungi(self): nome = self.campi["nome"].get().strip() email = self.campi["email"].get().strip() tel = self.campi["telefono"].get().strip() if not nome: messagebox.showwarning("Attenzione", "Il nome รจ obbligatorio!") return self.cur.execute("INSERT INTO contatti (nome,email,tel) VALUES (?,?,?)", (nome, email, tel)) self.conn.commit() for e in self.campi.values(): e.delete(0, tk.END) self.carica_contatti() def elimina(self): sel = self.tree.selection() if not sel: messagebox.showinfo("Info", "Seleziona un contatto da eliminare") return id_cont = self.tree.item(sel[0])["values"][0] if messagebox.askyesno("Conferma", "Eliminare questo contatto?"): self.cur.execute("DELETE FROM contatti WHERE id=?", (id_cont,)) self.conn.commit() self.carica_contatti() if __name__ == "__main__": root = tk.Tk() RubricaApp(root) root.mainloop()
SELECT * WHERE nome LIKE ?, oppure un pulsante "Modifica" che
pre-popola il form con i dati del contatto selezionato. Puoi anche esportare in CSV con il modulo
csv della libreria standard.
Perchรฉ si usano i ?
(placeholder) nelle query SQLite invece di formattare la stringa direttamente?
Dopo aver eseguito una INSERT,
cosa devi chiamare per salvare definitivamente i dati?
conn.save()cur.flush()conn.commit()conn.close()Continua con la prossima lezione per avanzare nel corso.
Nelle app reali non si scrive tutto in un unico file. Si usa il pattern MVC (Model-View-Controller) o almeno una separazione chiara tra logica e interfaccia. Questo rende il codice manutenibile e scalabile.
# Struttura consigliata per app Tkinter medio-grandi mia_app/ โโโ main.py # entry point โโโ model/ โ โโโ __init__.py โ โโโ database.py # logica dati โโโ view/ โ โโโ __init__.py โ โโโ main_view.py # finestra principale โ โโโ dialogs.py # finestre secondarie โโโ controller/ โ โโโ __init__.py โ โโโ app_ctrl.py # logica applicazione โโโ assets/ โโโ style.tcl # tema personalizzato
import tkinter as tk from tkinter import ttk from view.main_view import MainView from controller.app_ctrl import AppController class Application(tk.Tk): def __init__(self): super().__init__() self.title("La Mia App") self.geometry("900x600") self.minsize(700, 450) # Applica tema ttk self.style = ttk.Style(self) self.style.theme_use("clam") self._configura_tema() # Crea controller (che crea la view) self.controller = AppController(self) def _configura_tema(self): self.style.configure(".", font=("Segoe UI", 10), background="#f0f2f5" ) self.style.configure("TButton", padding=(10, 5), relief="flat", background="#1877f2", foreground="white" ) self.style.map("TButton", background=[("active", "#166fe5"), ("disabled", "#cccccc")] ) if __name__ == "__main__": app = Application() app.mainloop()
Puoi creare un tema scuro professionale configurando tutti i widget:
from tkinter import ttk def applica_tema_scuro(root): style = ttk.Style(root) style.theme_use("clam") # Colori BG = "#1e1e2e" BG2 = "#313244" FG = "#cdd6f4" ACCENT = "#89b4fa" MUTED = "#585b70" RED = "#f38ba8" GREEN = "#a6e3a1" # Sfondo globale style.configure(".", background=BG, foreground=FG, fieldbackground=BG2, troughcolor=BG2, selectbackground=ACCENT, selectforeground=BG, font=("Consolas", 10) ) # Frame e Label style.configure("TFrame", background=BG) style.configure("TLabel", background=BG, foreground=FG) style.configure("TLabelframe", background=BG, foreground=ACCENT, bordercolor=BG2) style.configure("TLabelframe.Label", background=BG, foreground=ACCENT, font=("Consolas", 10, "bold")) # Button style.configure("TButton", background=BG2, foreground=FG, bordercolor=MUTED, padding=(8, 4), relief="flat") style.map("TButton", background=[("active", ACCENT), ("pressed", ACCENT)], foreground=[("active", BG)]) # Accent Button style.configure("Accent.TButton", background=ACCENT, foreground=BG, font=("Consolas", 10, "bold")) style.map("Accent.TButton", background=[("active", "#74c7ec")]) # Entry style.configure("TEntry", fieldbackground=BG2, foreground=FG, insertcolor=FG, bordercolor=MUTED, padding=5) style.map("TEntry", bordercolor=[("focus", ACCENT)]) # Combobox style.configure("TCombobox", fieldbackground=BG2, foreground=FG, selectbackground=ACCENT, selectforeground=BG) # Notebook (tab) style.configure("TNotebook", background=BG, bordercolor=BG2) style.configure("TNotebook.Tab", background=BG2, foreground=MUTED, padding=(10, 4)) style.map("TNotebook.Tab", background=[("selected", BG)], foreground=[("selected", ACCENT)]) # Treeview style.configure("Treeview", background=BG2, foreground=FG, fieldbackground=BG2, rowheight=26) style.configure("Treeview.Heading", background=BG, foreground=ACCENT, font=("Consolas", 10, "bold")) style.map("Treeview", background=[("selected", ACCENT)], foreground=[("selected", BG)]) # Progressbar style.configure("TProgressbar", troughcolor=BG2, background=ACCENT, thickness=8) # Scrollbar style.configure("TScrollbar", troughcolor=BG, background=MUTED, arrowcolor=MUTED) style.map("TScrollbar", background=[("active", ACCENT)]) root.configure(bg=BG) return style
import tkinter as tk from tkinter import ttk root = tk.Tk() root.geometry("600x400") # โโ PanedWindow โ pannelli ridimensionabili โโ paned = ttk.PanedWindow(root, orient="horizontal") paned.pack(fill="both", expand=True) left_frame = ttk.Frame(paned, width=200) right_frame = ttk.Frame(paned) paned.add(left_frame, weight=1) paned.add(right_frame, weight=3) # โโ Spinbox โ selettore numerico โโ ttk.Label(left_frame, text="Etร :").pack(pady=5) spinbox = ttk.Spinbox(left_frame, from_=1, to=120, width=8, wrap=True) spinbox.pack(pady=3) # โโ OptionMenu โ menรน a tendina semplice โโ lingua = tk.StringVar(value="Italiano") ttk.Label(left_frame, text="Lingua:").pack(pady=5) menu = ttk.OptionMenu(left_frame, lingua, "Italiano", "Inglese", "Francese", "Spagnolo") menu.pack(pady=3) # โโ Scale โ slider โโ volume = tk.IntVar(value=50) ttk.Label(left_frame, text="Volume:").pack(pady=5) ttk.Scale(left_frame, from_=0, to=100, variable=volume, orient="horizontal").pack(pady=3, fill="x", padx=10) # โโ Text con scrollbar integrata โโ txt_frame = ttk.Frame(right_frame) txt_frame.pack(fill="both", expand=True, padx=10, pady=10) txt = tk.Text(txt_frame, wrap="word", font=("Consolas", 11)) scrollbar = ttk.Scrollbar(txt_frame, command=txt.yview) txt.configure(yscrollcommand=scrollbar.set) txt.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") root.mainloop()
import tkinter as tk from tkinter import ttk class AppState: """Stato condiviso tra tutti i componenti.""" def __init__(self): self.utente_loggato = None self.tema = "chiaro" self.lingua = "it" self._callbacks = {} # osservatori per il pattern Observer def on_change(self, evento, callback): """Registra un listener per un evento di stato.""" self._callbacks.setdefault(evento, []).append(callback) def emit(self, evento, data=None): """Notifica tutti i listener.""" for cb in self._callbacks.get(evento, []): cb(data) class MainApp(tk.Tk): def __init__(self): super().__init__() self.state = AppState() # unica sorgente di veritร self._crea_ui() def _crea_ui(self): lbl = ttk.Label(self, text="Nessun utente") lbl.pack(pady=20) # Ascolta cambiamenti di login self.state.on_change("login", lambda u: lbl.config(text=f"Benvenuto, {u}!")) ttk.Button(self, text="Simula login", command=lambda: self.state.emit("login", "Mario")).pack() MainApp().mainloop()
import tkinter as tk root = tk.Tk() canvas = tk.Canvas(root, width=500, height=400, bg="#f0f2f5") canvas.pack() # Crea un rettangolo trascinabile rect = canvas.create_rectangle(50, 50, 150, 100, fill="#1877f2", outline="#0d5bba", width=2) label = canvas.create_text(100, 75, text="Trascina!", fill="white") drag_data = {"x": 0, "y": 0} def start_drag(event): drag_data["x"] = event.x drag_data["y"] = event.y def do_drag(event): dx = event.x - drag_data["x"] dy = event.y - drag_data["y"] canvas.move(rect, dx, dy) canvas.move(label, dx, dy) drag_data["x"] = event.x drag_data["y"] = event.y canvas.tag_bind(rect, "<Button-1>", start_drag) canvas.tag_bind(rect, "<B1-Motion>", do_drag) canvas.tag_bind(label, "<Button-1>", start_drag) canvas.tag_bind(label, "<B1-Motion>", do_drag) root.mainloop()
Nel pattern MVC applicato a Tkinter, quale componente gestisce i dati?
Quale metodo di ttk.Style si usa per
cambiare il colore di sfondo di un TButton quando รจ in stato "active"?
style.configure("TButton", hover="#color")style.map("TButton", background=[("active","#color")])style.bind("TButton", active="#color")style.state("TButton", active="#color")Microsoft SQL Server รจ un sistema di gestione di database relazionali (RDBMS) enterprise, usato in aziende di tutto il mondo. A differenza di SQLite (file locale), SQL Server gira come servizio su un server e supporta connessioni multiple, transazioni avanzate, stored procedure e molto altro.
Per connettere Python a SQL Server si usa la libreria pyodbc insieme al driver ODBC di Microsoft:
# Installa pyodbc pip install pyodbc # Installa anche pandas (opzionale, per visualizzare dati) pip install pandas # Su Windows: scarica il driver ODBC da Microsoft # https://learn.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server # Verifica driver installati python -c "import pyodbc; print(pyodbc.drivers())"
import pyodbc # โโ Autenticazione Windows (Trusted Connection) โโ conn_str_windows = ( "DRIVER={ODBC Driver 18 for SQL Server};" "SERVER=localhost;" # o nome server/IP "DATABASE=MioDatabase;" "Trusted_Connection=yes;" "TrustServerCertificate=yes;" ) # โโ Autenticazione SQL (username/password) โโ conn_str_sql = ( "DRIVER={ODBC Driver 18 for SQL Server};" "SERVER=192.168.1.100,1433;" # IP,porta "DATABASE=MioDatabase;" "UID=sa;" "PWD=MiaPassword123!;" "TrustServerCertificate=yes;" ) # Connessione try: conn = pyodbc.connect(conn_str_windows) print("โ Connesso a SQL Server!") cur = conn.cursor() # Versione SQL Server cur.execute("SELECT @@VERSION") print(cur.fetchone()[0]) except pyodbc.Error as e: print(f"โ Errore: {e}") finally: if "conn" in dir(): conn.close()
import pyodbc from contextlib import contextmanager class SQLServerManager: def __init__(self, server, database, uid=None, pwd=None): if uid: self.conn_str = ( f"DRIVER={{ODBC Driver 18 for SQL Server}};" f"SERVER={server};DATABASE={database};" f"UID={uid};PWD={pwd};TrustServerCertificate=yes;" ) else: self.conn_str = ( f"DRIVER={{ODBC Driver 18 for SQL Server}};" f"SERVER={server};DATABASE={database};" f"Trusted_Connection=yes;TrustServerCertificate=yes;" ) self._conn = None def connetti(self): self._conn = pyodbc.connect(self.conn_str, autocommit=False) return self def disconnetti(self): if self._conn: self._conn.close() def esegui(self, query, params=None): """Esegui INSERT/UPDATE/DELETE con commit automatico.""" cur = self._conn.cursor() cur.execute(query, params or []) self._conn.commit() return cur def leggi(self, query, params=None): """Esegui SELECT e ritorna tutte le righe.""" cur = self._conn.cursor() cur.execute(query, params or []) return cur.fetchall() def leggi_uno(self, query, params=None): cur = self._conn.cursor() cur.execute(query, params or []) return cur.fetchone() def crea_tabella(self): self.esegui(""" IF NOT EXISTS ( SELECT * FROM sysobjects WHERE name='Dipendenti' AND xtype='U' ) CREATE TABLE Dipendenti ( Id INT IDENTITY(1,1) PRIMARY KEY, Nome NVARCHAR(100) NOT NULL, Cognome NVARCHAR(100) NOT NULL, Email NVARCHAR(200) UNIQUE, Reparto NVARCHAR(100), Stipendio DECIMAL(10,2), DataAssunzione DATE DEFAULT GETDATE() ) """)
# Usando SQLServerManager definito sopra db = SQLServerManager("localhost", "AziendaDB") db.connetti() db.crea_tabella() # โโ CREATE โโ db.esegui( "INSERT INTO Dipendenti (Nome, Cognome, Email, Reparto, Stipendio) " "VALUES (?, ?, ?, ?, ?)", ("Marco", "Rossi", "[email protected]", "IT", 35000.00) ) # โโ READ โ tutti โโ dipendenti = db.leggi("SELECT * FROM Dipendenti ORDER BY Cognome") for d in dipendenti: print(f"{d.Id} | {d.Nome} {d.Cognome} | {d.Reparto} | โฌ{d.Stipendio}") # โโ READ โ con filtro โโ it_team = db.leggi( "SELECT * FROM Dipendenti WHERE Reparto = ? AND Stipendio > ?", ("IT", 30000) ) # โโ UPDATE โโ db.esegui( "UPDATE Dipendenti SET Stipendio = ? WHERE Id = ?", (38000.00, 1) ) # โโ DELETE โโ db.esegui("DELETE FROM Dipendenti WHERE Id = ?", (5,)) # โโ Stored Procedure โโ db.esegui("EXEC sp_AggiornaStipendi @percentuale=?", (5,)) db.disconnetti()
Costruiamo un'app completa di gestione dipendenti con ricerca in tempo reale, inserimento, modifica ed eliminazione:
import tkinter as tk from tkinter import ttk, messagebox import pyodbc class GestioneDipendenti(tk.Tk): def __init__(self): super().__init__() self.title("๐ฅ Gestione Dipendenti") self.geometry("1000x600") self.configure(bg="#f8fafc") # Connessione DB self.db = SQLServerManager("localhost", "AziendaDB") try: self.db.connetti() self.db.crea_tabella() except Exception as e: messagebox.showerror("DB Error", f"Connessione fallita:\n{e}") self.destroy(); return # Variabili form self.var_nome = tk.StringVar() self.var_cognome = tk.StringVar() self.var_email = tk.StringVar() self.var_reparto = tk.StringVar() self.var_stipendio = tk.StringVar() self.var_cerca = tk.StringVar() self.id_selezionato = None self._build_ui() self._carica_dati() # Cerca in tempo reale self.var_cerca.trace("w", lambda *_: self._carica_dati()) self.protocol("WM_DELETE_WINDOW", self._chiudi) def _build_ui(self): # โโ Toolbar in cima โโ toolbar = tk.Frame(self, bg="#1877f2", height=46) toolbar.pack(fill="x"); toolbar.pack_propagate(False) tk.Label(toolbar, text="๐ฅ Gestione Dipendenti", fg="white", bg="#1877f2", font=("Segoe UI", 13, "bold")).pack(side="left", padx=16) # Cerca tk.Label(toolbar, text="๐", bg="#1877f2", fg="white").pack(side="right", padx=(0,4)) ttk.Entry(toolbar, textvariable=self.var_cerca, width=20).pack(side="right", pady=8, padx=4) # โโ Layout principale โโ main = tk.Frame(self, bg="#f8fafc") main.pack(fill="both", expand=True, padx=16, pady=16) # Form a sinistra form = tk.LabelFrame(main, text=" ๐ Dipendente ", font=("Segoe UI", 10, "bold"), bg="#f8fafc") form.pack(side="left", fill="y", padx=(0,12), ipadx=10, ipady=6) campi = [("Nome", self.var_nome), ("Cognome", self.var_cognome), ("Email", self.var_email), ("Reparto", self.var_reparto), ("Stipendio", self.var_stipendio)] for i, (lbl, var) in enumerate(campi): tk.Label(form, text=lbl+":", bg="#f8fafc", anchor="w").grid(row=i, column=0, sticky="w", pady=4) ttk.Entry(form, textvariable=var, width=22).grid( row=i, column=1, pady=4, padx=6) # Bottoni azione btn_frame = tk.Frame(form, bg="#f8fafc") btn_frame.grid(row=len(campi), column=0, columnspan=2, pady=12) for (txt, cmd, bg) in [ ("โ Aggiungi", self._aggiungi, "#42b72a"), ("โ๏ธ Modifica", self._modifica, "#1877f2"), ("๐๏ธ Elimina", self._elimina, "#fa383e"), ("๐ Reset", self._reset_form,"#65676b") ]: tk.Button(btn_frame, text=txt, command=cmd, bg=bg, fg="white", relief="flat", padx=10, pady=5, cursor="hand2").pack(fill="x", pady=2) # Tabella a destra cols = ("ID", "Nome", "Cognome", "Email", "Reparto", "Stipendio") self.tree = ttk.Treeview(main, columns=cols, show="headings") for c, w in zip(cols, [50, 120, 120, 200, 100, 90]): self.tree.heading(c, text=c) self.tree.column(c, width=w, anchor="center") vsb = ttk.Scrollbar(main, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=vsb.set) self.tree.pack(side="left", fill="both", expand=True) vsb.pack(side="left", fill="y") self.tree.bind("<<TreeviewSelect>>", self._seleziona) def _carica_dati(self): cerca = self.var_cerca.get().strip() if cerca: q = ("SELECT Id, Nome, Cognome, Email, Reparto, Stipendio " "FROM Dipendenti WHERE Nome LIKE ? OR Cognome LIKE ? OR Reparto LIKE ? " "ORDER BY Cognome") righe = self.db.leggi(q, (f"%{cerca}%",)*3) else: righe = self.db.leggi( "SELECT Id, Nome, Cognome, Email, Reparto, Stipendio FROM Dipendenti ORDER BY Cognome") for i in self.tree.get_children(): self.tree.delete(i) for r in righe: self.tree.insert("", tk.END, values=( r.Id, r.Nome, r.Cognome, r.Email, r.Reparto, f"โฌ{r.Stipendio:,.2f}" if r.Stipendio else "" )) def _seleziona(self, event): sel = self.tree.selection() if not sel: return vals = self.tree.item(sel[0])["values"] self.id_selezionato = vals[0] self.var_nome.set(vals[1]) self.var_cognome.set(vals[2]) self.var_email.set(vals[3] or "") self.var_reparto.set(vals[4] or "") self.var_stipendio.set(vals[5].replace("โฌ","").replace(",","") if vals[5] else "") def _aggiungi(self): if not self.var_nome.get() or not self.var_cognome.get(): messagebox.showwarning("Attenzione", "Nome e Cognome obbligatori!"); return try: self.db.esegui( "INSERT INTO Dipendenti (Nome,Cognome,Email,Reparto,Stipendio) VALUES (?,?,?,?,?)", (self.var_nome.get(), self.var_cognome.get(), self.var_email.get() or None, self.var_reparto.get() or None, float(self.var_stipendio.get()) if self.var_stipendio.get() else None)) self._carica_dati(); self._reset_form() except Exception as e: messagebox.showerror("Errore", str(e)) def _modifica(self): if not self.id_selezionato: messagebox.showinfo("Info", "Seleziona un dipendente dalla tabella"); return try: self.db.esegui( "UPDATE Dipendenti SET Nome=?,Cognome=?,Email=?,Reparto=?,Stipendio=? WHERE Id=?", (self.var_nome.get(), self.var_cognome.get(), self.var_email.get() or None, self.var_reparto.get() or None, float(self.var_stipendio.get()) if self.var_stipendio.get() else None, self.id_selezionato)) self._carica_dati(); self._reset_form() except Exception as e: messagebox.showerror("Errore", str(e)) def _elimina(self): if not self.id_selezionato: messagebox.showinfo("Info", "Seleziona un dipendente"); return if messagebox.askyesno("Conferma", "Eliminare questo dipendente?"): self.db.esegui("DELETE FROM Dipendenti WHERE Id=?", (self.id_selezionato,)) self._carica_dati(); self._reset_form() def _reset_form(self): for v in [self.var_nome, self.var_cognome, self.var_email, self.var_reparto, self.var_stipendio]: v.set("") self.id_selezionato = None def _chiudi(self): self.db.disconnetti() self.destroy() if __name__ == "__main__": GestioneDipendenti().mainloop()
# โโ JOIN tra tabelle โโ query_join = """ SELECT d.Nome, d.Cognome, r.NomeReparto, r.Budget FROM Dipendenti d INNER JOIN Reparti r ON d.Reparto = r.Id WHERE r.Budget > 100000 ORDER BY d.Cognome """ # โโ Aggregazioni โโ query_stats = """ SELECT Reparto, COUNT(*) AS NumDipendenti, AVG(Stipendio) AS StipendioMedio, MAX(Stipendio) AS StipendioMax, SUM(Stipendio) AS CostoTotale FROM Dipendenti GROUP BY Reparto HAVING COUNT(*) > 2 ORDER BY CostoTotale DESC """ # โโ Paginazione (SQL Server 2012+) โโ pagina = 1 per_pagina = 20 query_page = f""" SELECT * FROM Dipendenti ORDER BY Id OFFSET {(pagina-1) * per_pagina} ROWS FETCH NEXT {per_pagina} ROWS ONLY """ # โโ Transazione esplicita โโ try: cur = conn.cursor() cur.execute("BEGIN TRANSACTION") cur.execute("UPDATE Conti SET Saldo = Saldo - ? WHERE Id = ?", (500, 1)) cur.execute("UPDATE Conti SET Saldo = Saldo + ? WHERE Id = ?", (500, 2)) conn.commit() # salva SOLO se entrambe OK print("โ Trasferimento riuscito") except: conn.rollback() # annulla tutto se errore print("โ Transazione annullata")
Le Stored Procedure sono blocchi di SQL precompilati sul server. Sono piรน veloci delle query normali e centralizzano la logica di business nel database.
import pyodbc conn = pyodbc.connect(conn_str) cur = conn.cursor() # โโ Chiamare una Stored Procedure senza parametri โโ cur.execute("EXEC sp_GetAllDipendenti") righe = cur.fetchall() # โโ Con parametri di input โโ cur.execute("EXEC sp_GetDipendentiByReparto @Reparto=?", ("IT",)) righe = cur.fetchall() # โโ Con parametro di output โโ cur.execute(""" DECLARE @Totale INT; EXEC sp_ContaDipendenti @Reparto=?, @Totale=@Totale OUTPUT; SELECT @Totale; """, ("IT",)) totale = cur.fetchval() print(f"Dipendenti IT: {totale}") # โโ Esempio: creare una SP direttamente da Python โโ cur.execute(""" IF OBJECT_ID('sp_GetDipendentiByReparto') IS NOT NULL DROP PROCEDURE sp_GetDipendentiByReparto """) cur.execute(""" CREATE PROCEDURE sp_GetDipendentiByReparto @Reparto NVARCHAR(100) AS BEGIN SELECT Id, Nome, Cognome, Email, Stipendio FROM Dipendenti WHERE Reparto = @Reparto ORDER BY Cognome, Nome END """) conn.commit()
import pyodbc from tkinter import messagebox def esegui_sicuro(conn, query, params=None): """Wrapper con gestione errori completa.""" try: cur = conn.cursor() cur.execute(query, params or []) conn.commit() return True, cur except pyodbc.IntegrityError as e: conn.rollback() # Violazione UNIQUE, FK, NOT NULL ecc. messagebox.showerror("Errore dati", f"Dato duplicato o non valido:\n{e.args[1]}") return False, None except pyodbc.OperationalError as e: conn.rollback() # Connessione persa, timeout ecc. messagebox.showerror("Errore connessione", f"Problema con il database:\n{e.args[1]}\n\nRiprova tra poco.") return False, None except pyodbc.ProgrammingError as e: conn.rollback() # Errore SQL (tabella non trovata, sintassi ecc.) messagebox.showerror("Errore SQL", str(e)) return False, None # Codici di errore SQL Server comuni ERRORI_SQL = { 2627: "Valore duplicato: esiste giร un record con questi dati", 547: "Violazione chiave esterna: riferimento non valido", 515: "Campo obbligatorio mancante (NOT NULL)", 208: "Tabella non trovata", 4060: "Database non trovato o accesso negato", 18456:"Login fallito โ credenziali errate", } def descrivi_errore(e): if hasattr(e, "args") and e.args: codice = e.args[0] return ERRORI_SQL.get(codice, str(e)) return str(e)
Una funzionalitร comune nelle app gestionali รจ esportare i dati in Excel o CSV:
import csv, os from tkinter import filedialog, messagebox from datetime import datetime def esporta_csv(righe, intestazioni, titolo="dati"): """Esporta dati in CSV con dialogo salvataggio.""" nome_default = f"{titolo}_{datetime.now().strftime('%Y%m%d_%H%M')}.csv" percorso = filedialog.asksaveasfilename( defaultextension=".csv", initialfile=nome_default, filetypes=[("CSV", "*.csv"), ("Tutti", "*.*")] ) if not percorso: return with open(percorso, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f, delimiter=";") writer.writerow(intestazioni) writer.writerows(righe) messagebox.showinfo("Esportazione", f"โ {len(righe)} righe esportate in:\n{percorso}") os.startfile(percorso) # apre il file (Windows) # Installa openpyxl per Excel: pip install openpyxl try: import openpyxl from openpyxl.styles import Font, PatternFill, Alignment def esporta_excel(righe, intestazioni, titolo="report"): percorso = filedialog.asksaveasfilename( defaultextension=".xlsx", initialfile=f"{titolo}.xlsx", filetypes=[("Excel", "*.xlsx")] ) if not percorso: return wb = openpyxl.Workbook() ws = wb.active ws.title = titolo # Intestazioni con stile header_fill = PatternFill("solid", fgColor="1877F2") for col, nome in enumerate(intestazioni, 1): cell = ws.cell(1, col, nome) cell.font = Font(bold=True, color="FFFFFF") cell.fill = header_fill cell.alignment = Alignment(horizontal="center") # Dati for r, riga in enumerate(righe, 2): for c, val in enumerate(riga, 1): ws.cell(r, c, val) # Larghezza colonne automatica for col in ws.columns: max_w = max(len(str(c.value or "")) for c in col) ws.column_dimensions[col[0].column_letter].width = min(max_w + 4, 40) wb.save(percorso) os.startfile(percorso) except ImportError: print("Installa openpyxl per esportare in Excel: pip install openpyxl")
Mettendo insieme tutto โ Tkinter, ttk, SQL Server, CRUD, esportazione โ ecco la struttura di un gestionale aziendale completo:
# Struttura del progetto finale gestionale/ โโโ main.py # entry point e Application โโโ config.py # stringa connessione, costanti โโโ db/ โ โโโ manager.py # SQLServerManager โ โโโ dipendenti_repo.py # CRUD Dipendenti โ โโโ reparti_repo.py # CRUD Reparti โโโ ui/ โ โโโ main_window.py # finestra principale con menu โ โโโ dipendenti_tab.py # scheda gestione dipendenti โ โโโ reparti_tab.py # scheda gestione reparti โ โโโ report_tab.py # scheda report e grafici โ โโโ login_dialog.py # finestra di login โโโ utils/ โ โโโ esporta.py # CSV / Excel โ โโโ validazioni.py # controllo dati form โ โโโ tema.py # tema ttk personalizzato โโโ assets/ โโโ icone/ # file .png per toolbar
import tkinter as tk from tkinter import ttk, messagebox import pyodbc from db.manager import SQLServerManager from ui.main_window import MainWindow from ui.login_dialog import LoginDialog from utils.tema import applica_tema from config import SERVER, DATABASE class GestionaleApp(tk.Tk): def __init__(self): super().__init__() self.withdraw() # nasconde finestra durante login self.title("Gestionale Aziendale") self.geometry("1200x700") self.minsize(900, 550) applica_tema(self) # Login login = LoginDialog(self) self.wait_window(login) if not getattr(login, "confermato", False): self.destroy(); return # Connessione DB try: self.db = SQLServerManager( SERVER, DATABASE, uid=login.uid, pwd=login.pwd ).connetti() except pyodbc.Error as e: messagebox.showerror("Connessione", f"Impossibile connettersi al database:\n{e}") self.destroy(); return # Mostra finestra principale self.deiconify() MainWindow(self, self.db) self.protocol("WM_DELETE_WINDOW", self._chiudi) def _chiudi(self): if messagebox.askyesno("Esci", "Chiudere il gestionale?"): if hasattr(self, "db"): self.db.disconnetti() self.destroy() if __name__ == "__main__": GestionaleApp().mainloop()
Hai imparato tutto il necessario per costruire applicazioni desktop professionali con Python. Ecco il riepilogo completo:
Quale libreria Python si usa per connettersi a SQL Server tramite ODBC?
sqlite3pymssqlpyodbcsqlalchemyQuesto codice รจ pericoloso. Perchรฉ?
nome = entry.get()
cur.execute("SELECT * FROM Utenti WHERE Nome = '" + nome + "'")
?entry.get() non
funziona con pyodbccommit()
Come si chiama una Stored Procedure con parametro in pyodbc?
cur.call("sp_Nome", ("valore",))cur.execute("EXEC sp_Nome @Param=?", ("valore",))cur.procedure("sp_Nome", "valore")conn.stored_proc("sp_Nome", "valore")In una transazione con pyodbc, cosa succede se
chiami conn.rollback()?
Matplotlib รจ la libreria di visualizzazione dati piรน usata in Python. Permette di creare grafici a linee, barre, torta, scatter, istogrammi, e molto altro. Integrata con Tkinter, puoi costruire dashboard desktop professionali come quella in figura.
pip install matplotlib numpy โ NumPy รจ necessario per generare dati numerici efficientemente.
Matplotlib include un backend TkAgg che permette di incorporare grafici direttamente in una
finestra Tkinter tramite FigureCanvasTkAgg:
import tkinter as tk from tkinter import ttk import matplotlib matplotlib.use('TkAgg') # backend Tkinter from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg, NavigationToolbar2Tk # toolbar zoom/pan integrata ) import numpy as np root = tk.Tk() root.title("Grafico Tkinter") root.geometry("900x600") root.configure(bg="#1e1e2e") # 1. Crea la Figure Matplotlib fig = Figure(figsize=(9, 5), dpi=100, facecolor="#1e1e2e") ax = fig.add_subplot(111) ax.set_facecolor("#181825") # 2. Disegna il grafico x = np.linspace(0, 10, 200) ax.plot(x, np.sin(x), color="#89b4fa", linewidth=2, label="sin(x)") ax.plot(x, np.cos(x), color="#a6e3a1", linewidth=2, label="cos(x)") ax.set_title("Funzioni Trigonometriche", color="#cdd6f4") ax.set_xlabel("x", color="#a6adc8") ax.set_ylabel("y", color="#a6adc8") ax.tick_params(colors="#a6adc8") ax.legend(facecolor="#313244", labelcolor="#cdd6f4") ax.grid(alpha=0.2, color="#45475a") # 3. Embedding in Tkinter canvas = FigureCanvasTkAgg(fig, master=root) canvas.draw() canvas.get_tk_widget().pack(fill="both", expand=True) # 4. Toolbar opzionale (zoom, pan, salva) toolbar = NavigationToolbar2Tk(canvas, root) toolbar.update() root.mainloop()
import numpy as np # Dati vendite mensili (Gen โ Dic) mesi = ['Gen','Feb','Mar','Apr','Mag','Giu', 'Lug','Ago','Set','Ott','Nov','Dic'] vendite_A = [120,135,148,162,155,170, 180,165,175,190,210,225] vendite_B = [95, 102,118,125,140,138, 152,160,158,172,185,195] ax.plot(mesi, vendite_A, 'o-', color="#89b4fa", linewidth=2, markersize=5, label="Prodotto A") ax.plot(mesi, vendite_B, 's-', color="#a6e3a1", linewidth=2, markersize=5, label="Prodotto B") # Area riempita sotto la curva ax.fill_between(mesi, vendite_A, alpha=0.1, color="#89b4fa") ax.set_title("Trend Vendite Mensili", color="#cdd6f4", fontsize=14) ax.set_xlabel("Mese", color="#a6adc8") ax.set_ylabel("Vendite (kโฌ)", color="#a6adc8") ax.legend() ax.grid(axis="y", alpha=0.2)
reparti = ['Nord', 'Sud', 'Est', 'Ovest', 'Centro'] fatturato = [340, 210, 180, 420, 290] obiettivo = [300, 250, 200, 380, 310] x = np.arange(len(reparti)) w = 0.35 # larghezza barra bars1 = ax.bar(x - w/2, fatturato, w, label="Reale", color="#89b4fa") bars2 = ax.bar(x + w/2, obiettivo, w, label="Obiettivo", color="#313244") # Etichette sopra le barre ax.bar_label(bars1, fmt='%dk', color="#cdd6f4", fontsize=8) ax.set_xticks(x) ax.set_xticklabels(reparti) ax.set_title("Fatturato vs Obiettivo (kโฌ)")
settori = ['Tech', 'Finance', 'Health', 'Energy', 'Retail'] quote = [35, 20, 18, 15, 12] colori = ['#89b4fa', '#a6e3a1', '#f38ba8', '#fab387', '#94e2d5'] wedges, texts, autotexts = ax.pie( quote, labels=settori, colors=colori, autopct='%1.0f%%', pctdistance=0.75, startangle=90, wedgeprops=dict(width=0.6, # crea effetto donut edgecolor='#1e1e2e', linewidth=2) ) # Stile testi for text in texts: text.set_color("#cdd6f4") for autotext in autotexts: autotext.set_color("#1e1e2e") autotext.set_fontweight("bold") ax.set_title("Quote di Mercato", color="#cdd6f4")
np.random.seed(42) n = 150 investimento = np.random.uniform(0, 100, n) profitto = investimento * 0.8 + np.random.normal(0, 12, n) colori_pts = np.random.uniform(0, 1, n) # colore per categoria dimensioni = np.random.uniform(20, 200, n) # dimensione punto scatter = ax.scatter( investimento, profitto, c=colori_pts, # colore da array (colormap automatica) s=dimensioni, # dimensione punti cmap='plasma', alpha=0.7, edgecolors='none' ) # Linea di regressione z = np.polyfit(investimento, profitto, 1) p = np.poly1d(z) x_line = np.linspace(investimento.min(), investimento.max(), 100) ax.plot(x_line, p(x_line), '--', color="#f9e2af", linewidth=2) ax.set_title("Investimento vs Profitto") ax.set_xlabel("Investimento (kโฌ)") ax.set_ylabel("Profitto (kโฌ)") fig.colorbar(scatter, ax=ax, label="Categoria")
np.random.seed(0) valutazioni = np.random.normal(loc=7.2, scale=1.5, size=500) valutazioni = np.clip(valutazioni, 0, 10) n, bins, patches = ax.hist( valutazioni, bins=20, color="#cba6f7", edgecolor="#1e1e2e", alpha=0.85 ) # Linee statistiche media = valutazioni.mean() mediana = np.median(valutazioni) ax.axvline(media, color="#f38ba8", linestyle="--", linewidth=2, label=f"Media {media:.1f}") ax.axvline(mediana, color="#a6e3a1", linestyle=":", linewidth=2, label=f"Mediana {mediana:.1f}") ax.set_title("Soddisfazione Clienti (0-10)") ax.set_xlabel("Punteggio") ax.set_ylabel("N. Clienti") ax.legend()
t = np.linspace(0, 4 * np.pi, 300) A = 0.5 + 0.5 * np.sin(t * 0.8) B = 0.5 + 0.5 * np.sin(t * 0.8 + np.pi / 3) C = 0.5 + 0.5 * np.sin(t * 0.8 + 2 * np.pi / 3) ax.stackplot(t, A, B, C, labels=['A', 'B', 'C'], colors=['#89b4fa', '#94e2d5', '#a6adc8'], alpha=0.8) ax.set_xlim(0, t.max()) ax.set_ylim(0) ax.set_title("Segnale Multi-canale") ax.legend(loc="upper right")
Costruiamo la dashboard professionale visibile in figura: 6 grafici in un layout a griglia con sidebar di navigazione, tema scuro e aggiornamento dinamico.
import tkinter as tk from tkinter import ttk import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot as plt from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import numpy as np # โโ Palette colori โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ BG = "#0d0d0d" SURFACE = "#1a1a1a" SIDEBAR = "#111111" ACCENT = "#00bcd4" TEXT = "#e0e0e0" MUTED = "#555555" COLORS = ["#00bcd4","#4caf50","#ff9800","#e91e63","#9c27b0"] plt.rcParams.update({ "figure.facecolor": SURFACE, "axes.facecolor": "#141414", "axes.edgecolor": "#333", "axes.labelcolor": MUTED, "text.color": TEXT, "xtick.color": MUTED, "ytick.color": MUTED, "grid.color": "#222", "grid.alpha": 0.5, "font.family": "sans-serif", "font.size": 9, }) class Dashboard(tk.Tk): def __init__(self): super().__init__() self.title("Dashboard Matplotlib โ Tkinter Desktop App") self.configure(bg=BG) self.state("zoomed") # finestra massimizzata self.vista_corrente = tk.StringVar(value="Dashboard") self._build_layout() self._show_view("Dashboard") # โโ Layout principale โโโโโโโโโโโโโโโโโโโโโโโโ def _build_layout(self): # Sidebar sinistra self.sidebar = tk.Frame(self, bg=SIDEBAR, width=180) self.sidebar.pack(side="left", fill="y") self.sidebar.pack_propagate(False) # Logo tk.Label(self.sidebar, text="โ", fg=ACCENT, bg=SIDEBAR, font=("Arial", 24)).pack(pady=(20,4)) tk.Label(self.sidebar, text="DATA DASH", fg=TEXT, bg=SIDEBAR, font=("Arial", 13, "bold")).pack() tk.Label(self.sidebar, text="Matplotlib + Tkinter", fg=MUTED, bg=SIDEBAR, font=("Arial", 8)).pack(pady=(0,16)) # Separatore tk.Frame(self.sidebar, bg="#222", height=1).pack(fill="x", padx=16) # Voci di navigazione tk.Label(self.sidebar, text="GRAFICI", fg=MUTED, bg=SIDEBAR, font=("Arial", 8), anchor="w").pack( fill="x", padx=16, pady=(16,4)) self.nav_buttons = {} voci = [ ("โ ", "Dashboard"), ("โ", "Linee"), ("โฎ", "Barre"), ("โ", "Torta"), ("ยท", "Scatter"), ("โ", "Istogramma"), ("~", "Area"), ] for icona, nome in voci: btn = tk.Button( self.sidebar, text=f" {icona} {nome}", fg=TEXT, bg=SIDEBAR, activebackground="#1e1e1e", activeforeground=ACCENT, relief="flat", font=("Arial", 11), anchor="w", command=lambda n=nome: self._show_view(n) ) btn.pack(fill="x", padx=8, pady=1) self.nav_buttons[nome] = btn # Versione tk.Label(self.sidebar, text="v2.0 โข matplotlib", fg=MUTED, bg=SIDEBAR, font=("Arial", 7)).pack( side="bottom", pady=10) # Area principale self.main = tk.Frame(self, bg=BG) self.main.pack(side="right", fill="both", expand=True) # Header header = tk.Frame(self.main, bg=BG) header.pack(fill="x", padx=20, pady=12) self.title_lbl = tk.Label(header, text="Dashboard", fg=TEXT, bg=BG, font=("Arial", 20, "bold")) self.title_lbl.pack(side="left") # Badge librerie for lib in ["Matplotlib", "NumPy", "Tkinter"]: tk.Label(header, text=lib, fg=ACCENT, bg=BG, font=("Arial", 8)).pack(side="right", padx=4) # Canvas container self.canvas_frame = tk.Frame(self.main, bg=BG) self.canvas_frame.pack(fill="both", expand=True, padx=10) # โโ Navigazione โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ def _show_view(self, nome): # Evidenzia bottone attivo for n, btn in self.nav_buttons.items(): btn.config(bg="#1e1e1e" if n==nome else SIDEBAR, fg=ACCENT if n==nome else TEXT) self.title_lbl.config(text=nome) # Pulisce canvas precedente for w in self.canvas_frame.winfo_children(): w.destroy() # Costruisce il grafico metodo = getattr(self, f"_view_{nome.lower()}", self._view_dashboard) fig = metodo() canvas = FigureCanvasTkAgg(fig, master=self.canvas_frame) canvas.draw() canvas.get_tk_widget().pack(fill="both", expand=True) # โโ Vista Dashboard (6 grafici) โโโโโโโโโโโโโโ def _view_dashboard(self): fig, axes = plt.subplots(3, 2, figsize=(14, 9)) fig.tight_layout(pad=3.0) np.random.seed(42) mesi = ['Gen','Mar','Mag','Lug','Set','Nov'] # Linee ax = axes[0][0] for i, c in enumerate(COLORS[:3]): ax.plot(mesi, np.random.randint(80,200,6), 'o-', color=c, label=chr(65+i), linewidth=1.8) ax.set_title("Trend Vendite Mensili"); ax.legend(); ax.grid(True) # Barre ax = axes[0][1] reg = ['Nord','Sud','Est','Ovest','Centro'] fat = [340,210,180,420,290]; obj = [300,250,200,380,310] x = np.arange(5); w=0.35 ax.bar(x-w/2, fat, w, color=ACCENT, label="Reale") ax.bar(x+w/2, obj, w, color="#333", label="Obiettivo") ax.set_xticks(x); ax.set_xticklabels(reg) ax.set_title("Fatturato vs Obiettivo (kโฌ)"); ax.legend() # Torta donut ax = axes[1][0] ax.pie([35,20,18,15,12], labels=['Tech','Finance','Health','Energy','Retail'], colors=COLORS, autopct='%1.0f%%', wedgeprops=dict(width=0.6, edgecolor=SURFACE, linewidth=2)) ax.set_title("Quote di Mercato") # Scatter ax = axes[1][1] inv = np.random.uniform(0,100,150) pro = inv * 0.8 + np.random.normal(0,12,150) sc = ax.scatter(inv, pro, c=np.random.rand(150), s=np.random.uniform(20,200,150), cmap='plasma', alpha=0.7) z=np.polyfit(inv,pro,1); p=np.poly1d(z) ax.plot(np.sort(inv), p(np.sort(inv)), '--', color="#f9e2af", lw=2) ax.set_title("Investimento vs Profitto") fig.colorbar(sc, ax=ax) # Istogramma ax = axes[2][0] vals = np.clip(np.random.normal(7.2,1.5,500), 0, 10) ax.hist(vals, bins=20, color="#cba6f7", edgecolor=SURFACE, alpha=0.85) ax.axvline(vals.mean(), color="#f38ba8", lw=2, linestyle="--", label=f"Media {vals.mean():.1f}") ax.axvline(np.median(vals), color="#a6e3a1", lw=2, linestyle=":", label=f"Mediana {np.median(vals):.1f}") ax.set_title("Soddisfazione Clienti (0-10)"); ax.legend() # Area stackplot ax = axes[2][1] t = np.linspace(0, 4*np.pi, 300) A = 0.5+0.5*np.sin(t*0.8) B = 0.5+0.5*np.sin(t*0.8+np.pi/3) C = 0.5+0.5*np.sin(t*0.8+2*np.pi/3) ax.stackplot(t, A, B, C, labels=['A','B','C'], colors=[ACCENT,"#4caf50","#555"], alpha=0.8) ax.set_title("Segnale Multi-canale"); ax.legend(loc="upper right") return fig # โโ Viste singole โโโโโโโโโโโโโโโโโโโโโโโโโโโ def _view_linee(self): fig, ax = plt.subplots(figsize=(12,7)) mesi = ['Gen','Feb','Mar','Apr','Mag','Giu', 'Lug','Ago','Set','Ott','Nov','Dic'] np.random.seed(1) for i,c in enumerate(COLORS[:3]): y = np.random.randint(80,220,12) ax.plot(mesi,y,'o-',color=c,lw=2,ms=5,label=f"Serie {i+1}") ax.fill_between(mesi,y,alpha=0.05,color=c) ax.set_title("Trend Annuale",fontsize=14); ax.legend(); ax.grid(True) return fig def _view_barre(self): fig, ax = plt.subplots(figsize=(12,7)) cat = ['Q1','Q2','Q3','Q4'] for i,c in enumerate(COLORS[:3]): ax.bar(np.arange(4)+i*0.25, np.random.randint(100,500,4), 0.25, color=c, label=f"Prodotto {i+1}") ax.set_xticks(np.arange(4)+0.25); ax.set_xticklabels(cat) ax.set_title("Vendite per Trimestre",fontsize=14); ax.legend() return fig def _view_torta(self): fig, ax = plt.subplots(figsize=(10,8)) ax.pie([30,25,20,15,10], labels=['A','B','C','D','E'], colors=COLORS, autopct='%1.1f%%', startangle=90, wedgeprops=dict(width=0.55,edgecolor=SURFACE,linewidth=2), textprops=dict(color=TEXT)) ax.set_title("Distribuzione Portfolio",fontsize=14) return fig def _view_scatter(self): fig, ax = plt.subplots(figsize=(12,7)) np.random.seed(5) for c in COLORS: x = np.random.normal(50,20,80) y = x * 0.7 + np.random.normal(0,10,80) ax.scatter(x,y,color=c,alpha=0.6,s=60) ax.set_title("Correlazione Multi-cluster",fontsize=14) ax.set_xlabel("Variabile X"); ax.set_ylabel("Variabile Y") return fig def _view_istogramma(self): fig, ax = plt.subplots(figsize=(12,7)) np.random.seed(7) dati = np.clip(np.random.normal(7,2,1000),0,10) ax.hist(dati,bins=30,color=COLORS[4],edgecolor=SURFACE,alpha=0.9) ax.axvline(dati.mean(),color=COLORS[2],lw=2,linestyle="--", label=f"ฮผ={dati.mean():.2f}") ax.set_title("Distribuzione NPS",fontsize=14); ax.legend() return fig def _view_area(self): fig, ax = plt.subplots(figsize=(12,7)) t = np.linspace(0,4*np.pi,400) segnali = [np.0.5+np.0.5*np.sin(t*0.8+i*np.pi/3) for i in range(4)] ax.stackplot(t,*segnali, labels=[f"Canale {i+1}" for i in range(4)], colors=COLORS, alpha=0.75) ax.set_title("Segnali Multi-canale",fontsize=14) ax.legend(loc="upper right") return fig if __name__ == "__main__": Dashboard().mainloop()
Combinando after() di Tkinter con canvas.draw() puoi creare grafici che si
aggiornano automaticamente:
import tkinter as tk import matplotlib; matplotlib.use('TkAgg') from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import numpy as np from collections import deque root = tk.Tk() root.title("Monitoraggio CPU (Simulato)") root.configure(bg="#0d0d0d") fig = Figure(figsize=(10, 4), dpi=100, facecolor="#141414") ax = fig.add_subplot(111) ax.set_facecolor("#0d0d0d") ax.set_ylim(0, 100) ax.set_ylabel("CPU %", color="#aaa") ax.tick_params(colors="#555") ax.grid(alpha=0.15, color="#333") canvas = FigureCanvasTkAgg(fig, master=root) canvas.get_tk_widget().pack(fill="both", expand=True) # Buffer circolare (ultimi 60 campioni) N = 60 dati_cpu = deque([0] * N, maxlen=N) linea, = ax.plot([], [], color="#00bcd4", linewidth=1.5) fill = [None] def aggiorna(): # Simula valore CPU (sostituire con psutil.cpu_percent()) nuovo = np.random.uniform(10, 90) dati_cpu.append(nuovo) x = list(range(N)) y = list(dati_cpu) linea.set_data(x, y) ax.set_xlim(0, N - 1) if fill[0]: fill[0].remove() fill[0] = ax.fill_between(x, y, alpha=0.15, color="#00bcd4") canvas.draw_idle() # piรน efficiente di draw() root.after(500, aggiorna) # aggiorna ogni 500ms aggiorna() root.mainloop()
from tkinter import filedialog def salva_grafico(fig): percorso = filedialog.asksaveasfilename( defaultextension=".png", filetypes=[ ("PNG Image", "*.png"), ("PDF", "*.pdf"), ("SVG Vector", "*.svg"), ("JPEG", "*.jpg"), ], initialfile="grafico" ) if percorso: fig.savefig( percorso, dpi=150, # risoluzione alta stampa bbox_inches="tight", # nessun bordo bianco extra facecolor=fig.get_facecolor() ) from tkinter import messagebox messagebox.showinfo("Salvato", f"โ Grafico salvato:\n{percorso}") # Aggiungi bottone salva in Tkinter tk.Button(root, text="๐พ Salva PNG", command=lambda: salva_grafico(fig)).pack(pady=6)
Quale classe si usa per incorporare un grafico Matplotlib in una finestra Tkinter?
MatplotlibTkFigureCanvasTkAgg
TkCanvas(fig)plt.show(master=root)
Per aggiornare un grafico ogni 500ms senza bloccare la GUI, quale coppia di metodi si usa?
time.sleep(0.5) +
canvas.draw()root.after(500, aggiorna) + canvas.draw_idle()threading.Timer(0.5, aggiorna) + canvas.draw()root.update() +
fig.refresh()Quale metodo di Matplotlib salva la figura in un file PNG ad alta risoluzione?
plt.export("file.png")canvas.save("file.png")fig.savefig("file.png", dpi=150, bbox_inches="tight")fig.write_png("file.png")Complimenti! Hai padroneggiato tutto lo stack Python Desktop: