CLASSE TERZA • MODULO 05

GUI con Tkinter

Crea applicazioni desktop professionali con interfacce grafiche in Python

🖥️ Cos'è Tkinter?

Tkinter è la libreria standard di Python per creare interfacce grafiche (GUI - Graphical User Interface). È inclusa in tutte le installazioni di Python, quindi non richiede installazioni aggiuntive!

🚀 Nativamente in Python

Non serve installare nulla, è già disponibile

🎨 Cross-Platform

Funziona su Windows, Mac e Linux senza modifiche

📚 Facile da Imparare

Sintassi semplice, perfetta per iniziare con le GUI

💡 Perché imparare Tkinter? Mentre esistono librerie GUI più moderne (PyQt, Kivy, ecc.), Tkinter è perfetta per iniziare perché:
  • È già installata con Python
  • Ha una curva di apprendimento dolce
  • Ti insegna i concetti fondamentali delle GUI
  • È perfetta per applicazioni semplici e medie

🖥️ Ambiente di Esecuzione per questo Modulo

⚠️ IMPORTANTE: Questo modulo richiede un ambiente desktop

Le applicazioni GUI con Tkinter non funzionano su Google Colab perché richiedono un display grafico desktop.

✅ RACCOMANDATO

PyCharm, VS Code o IDE desktop

  • Installa Python sul tuo computer
  • Apri i file .py con il tuo IDE
  • Esegui normalmente con F5/Run

✅ FUNZIONA

Anaconda Navigator (Jupyter Notebook locale)

  • Avvia Jupyter dal menu Start
  • Crea nuovo notebook Python 3
  • Esegui le celle normalmente

❌ NON FUNZIONA

Google Colab / Ambiente cloud

  • Nessun display grafico disponibile
  • Tkinter non può creare finestre
  • Usa solo per codice non-GUI

1 Verifica il tuo ambiente

🚫 COPIA BLOCCATA
# ESEMPIO: Test se Tkinter funziona import tkinter as tk try: # Prova a creare una finestra test_window = tk.Tk() test_window.title("Test Tkinter") test_window.geometry("300x200") tk.Label(test_window, text="✅ Tkinter funziona!", font=("Arial", 14)).pack(pady=50) print("SUCCESSO: Tkinter è installato e funzionante!") print("Puoi chiudere la finestra per continuare.") test_window.mainloop() except Exception as e: print(f"ERRORE: {e}") print("\n🛠️ SOLUZIONE:") print("1. Assicurati di eseguire su computer locale, NON su Colab") print("2. Installa/reinstalla Python da python.org") print("3. Usa PyCharm, VS Code o Anaconda")
💡 Perché questa differenza?

Tkinter crea finestre reali del sistema operativo (come qualsiasi programma che usi ogni giorno). Google Colab è un ambiente server senza interfaccia grafica, quindi non può mostrare finestre.

Implicazione didattica: Questo è perfetto per imparare! Stai creando applicazioni desktop REALI, non script da terminale.

🎯 STRATEGIA DI STUDIO PER QUESTO MODULO:

  1. LEGGI le spiegazioni qui sul browser
  2. SCOARICA gli esempi sul tuo computer
  3. APRI con PyCharm/VS Code/Anaconda
  4. ESEGUI e modifica il codice
  5. SPERIMENTA con le tue variazioni

🚀 La Tua Prima Finestra Tkinter

1 Importazione di Base

🚫 COPIA BLOCCATA
# Importa Tkinter (modo standard) import tkinter as tk from tkinter import ttk # Per widget più moderni

📖 Spiegazione dell'Import:

import tkinter as tk: Importa l'intero modulo tkinter con l'alias tk. Questo è il modo standard usato da tutti.

from tkinter import ttk: Importa il modulo ttk (Themed Tkinter) che contiene widget con stili più moderni e uniformi tra diversi sistemi operativi.

tk vs ttk:

Modulo Widget Caratteristiche
tk Classici Stile nativo del sistema operativo
ttk Themed Stile uniforme cross-platform, più moderno

2 Finestra Base con Titolo

🚫 COPIA BLOCCATA
# Creazione della finestra principale root = tk.Tk() # Configurazione della finestra root.title("La Mia Prima App") # Titolo della finestra root.geometry("400x300") # Dimensioni: larghezza x altezza (in pixel) root.resizable(True, True) # Ridimensionabile orizzontalmente e verticalmente # Avvio del loop principale root.mainloop()

📖 Analisi Comando per Comando:

root = tk.Tk(): Crea l'oggetto finestra principale.

  • root: Nome convenzionale per la finestra principale (puoi usare qualsiasi nome)
  • tk.Tk(): Costruttore che crea una nuova finestra
  • Importante: Deve essere creato una sola volta per applicazione

root.title("La Mia Prima App"): Imposta il titolo della finestra.

  • Compare nella barra del titolo della finestra
  • Stringa qualsiasi

root.geometry("400x300"): Imposta le dimensioni iniziali.

  • "400x300": Formato stringa "larghezzaxaltezza"
  • Unitià: pixel
  • Posizione opzionale: "400x300+100+50" (posizione x=100, y=50)

root.resizable(True, True): Controlla il ridimensionamento.

Valore Primo Parametro Secondo Parametro
True Ridimensionabile orizzontalmente Ridimensionabile verticalmente
False Larghezza fissa Altezza fissa

root.mainloop(): Avvia il loop principale dell'applicazione.

  • FUNZIONE CRITICA: Senza questo, la finestra non si apre!
  • Gestisce eventi (click, tastiera, ridimensionamento, ecc.)
  • Rimane in esecuzione finché non si chiude la finestra
  • Deve essere l'ultimo comando del programma

👁️ Anteprima della Finestra:

🔵 La Mia Prima App
Finestra 400×300 pixel
Ridimensionabile in entrambe le direzioni

🧱 Widget Fondamentali di Tkinter

I widget sono gli elementi dell'interfaccia: bottoni, etichette, campi testo, ecc.

1. Label - Etichetta di Testo

🚫 COPIA BLOCCATA
import tkinter as tk root = tk.Tk() root.title("Esempio Label") root.geometry("300x200") # Creazione di una Label label = tk.Label( root, # Padre (dove posizionare il widget) text="Ciao, Tkinter!", # Testo da visualizzare font=("Arial", 16), # Font: famiglia e dimensione fg="white", # Colore testo (foreground) bg="blue", # Colore sfondo (background) padx=20, # Padding orizzontale pady=10 # Padding verticale ) # Posizionamento del widget (pack è il layout manager più semplice) label.pack() root.mainloop()

📖 Parametri Principali di Label:

Parametro Esempio Descrizione
text "Ciao, Tkinter!" Testo da visualizzare
font ("Arial", 16) Tupla: (famiglia, dimensione) o (famiglia, dimensione, stile)
fg "white" o "#FFFFFF" Colore testo (foreground)
bg "blue" o "#0000FF" Colore sfondo (background)
padx 20 Spazio orizzontale interno (padding)
pady 10 Spazio verticale interno (padding)
anchor "w" (west/left) Allineamento: n, s, e, w, ne, nw, se, sw, center

label.pack(): Posiziona il widget nella finestra.

  • pack(): Layout manager semplice (impilamento automatico)
  • Altri layout manager: grid() (griglia), place() (posizione assoluta)

2. Button - Bottone con Evento Click

🚫 COPIA BLOCCATA
import tkinter as tk from tkinter import messagebox # Per finestre di dialogo def saluta(): """Funzione chiamata quando si clicca il bottone""" messagebox.showinfo("Saluto", "Ciao! Hai cliccato il bottone!") root = tk.Tk() root.title("Esempio Button") root.geometry("300x200") # Creazione del bottone bottone = tk.Button( root, text="Cliccami!", command=saluta, # Funzione da chiamare al click font=("Arial", 14), bg="green", fg="white", activebackground="darkgreen", # Colore quando premuto activeforeground="yellow", # Testo quando premuto relief="raised", # Stile del bordo: flat, raised, sunken, groove, ridge bd=3 # Spessore del bordo (borderwidth) ) bottone.pack(pady=50) # Aggiunge padding verticale di 50 pixel root.mainloop()

📖 Funzioni di Callback e Parametri Button:

Funzioni di Callback:

  • command=saluta: Specifica la funzione da chiamare al click
  • La funzione non ha parentesi: saluta non saluta()
  • Se metti le parentesi, la funzione viene chiamata immediatamente (all'avvio)

Parametri Specifici di Button:

Parametro Valori Tipici Effetto
command nome_funzione Funzione chiamata al click (callback)
activebackground "darkgreen" Colore sfondo quando il bottone è premuto
activeforeground "yellow" Colore testo quando il bottone è premuto
relief "raised" Stile 3D del bordo
state "normal", "disabled" Stato del bottone (cliccabile o disabilitato)

messagebox.showinfo(): Mostra una finestra di dialogo informativa.

  • Altri tipi: showwarning(), showerror(), askyesno(), askokcancel()
  • Importare: from tkinter import messagebox

3. Entry - Campo di Input Testo

🚫 COPIA BLOCCATA
import tkinter as tk from tkinter import messagebox def mostra_testo(): """Mostra il testo inserito nell'Entry""" testo_inserito = entry.get() # Ottieni il testo dall'Entry if testo_inserito: # Se non è vuoto messagebox.showinfo("Testo Inserito", f"Hai scritto: {testo_inserito}") entry.delete(0, tk.END) # Pulisci il campo else: messagebox.showwarning("Attenzione", "Per favore, inserisci del testo!") root = tk.Tk() root.title("Esempio Entry") root.geometry("400x250") # Label descrittiva tk.Label(root, text="Inserisci il tuo nome:", font=("Arial", 12)).pack(pady=10) # Campo Entry per input testo entry = tk.Entry( root, width=30, # Larghezza in caratteri font=("Arial", 12), bg="lightyellow", # Colore sfondo fg="darkblue", # Colore testo justify="center" # Allineamento testo: left, center, right ) entry.pack(pady=10) # Bottone per inviare tk.Button( root, text="Invia", command=mostra_testo, bg="blue", fg="white" ).pack(pady=10) # Entry con testo predefinito entry_default = tk.Entry(root, width=30, font=("Arial", 12)) entry_default.pack(pady=10) entry_default.insert(0, "Testo predefinito") # Inserisce testo alla posizione 0 # Entry password (nasconde i caratteri) tk.Label(root, text="Password:", font=("Arial", 12)).pack(pady=(20,5)) entry_password = tk.Entry( root, width=30, font=("Arial", 12), show="*" # Sostituisce i caratteri con * ) entry_password.pack() root.mainloop()

📖 Metodi Fondamentali di Entry:

entry.get(): Ottiene il testo inserito nell'Entry.

  • Restituisce una stringa
  • Stringa vuota "" se non c'è testo

entry.delete(inizio, fine): Cancella testo.

  • 0: Primo carattere
  • tk.END: Costante che indica la fine del testo
  • entry.delete(0, tk.END): Cancella tutto il testo

entry.insert(posizione, testo): Inserisce testo.

  • 0: All'inizio
  • tk.END: Alla fine
  • entry.insert(0, "Ciao"): Inserisce "Ciao" all'inizio

show="*": Nasconde i caratteri (per password).

  • show="*": Mostra asterischi
  • show="#": Mostra cancelletti
  • show="" (default): Mostra testo normale

📐 Layout Managers: Organizzare i Widget

Tkinter offre 3 modi per posizionare i widget: pack(), grid(), place().

🎯 Confronto Layout Managers:

pack()
Impilamento automatico
grid()
Griglia righe/colonne
place()
Posizione assoluta

1. pack() - Il più semplice

🚫 COPIA BLOCCATA
import tkinter as tk root = tk.Tk() root.title("Esempio pack()") root.geometry("350x250") # pack() impila i widget verticalmente (di default) tk.Label(root, text="Primo", bg="red", fg="white", width=20).pack() tk.Label(root, text="Secondo", bg="green", fg="white", width=20).pack() tk.Label(root, text="Terzo", bg="blue", fg="white", width=20).pack() # Parametri di pack() tk.Label(root, text="Sinistra", bg="orange", fg="black").pack(side="left", padx=5, pady=5) tk.Label(root, text="Destra", bg="purple", fg="white").pack(side="right", padx=5, pady=5) tk.Label(root, text="Alto", bg="cyan", fg="black").pack(side="top", fill="x", padx=10) tk.Label(root, text="Basso", bg="magenta", fg="white").pack(side="bottom", fill="x", padx=10) # fill e expand tk.Label(root, text="Fill X", bg="yellow", fg="black").pack(fill="x", padx=20, pady=5) tk.Label(root, text="Fill Both + Expand", bg="gray", fg="white").pack(fill="both", expand=True, padx=10, pady=10) root.mainloop()

📖 Parametri di pack():

Parametro Valori Descrizione
side "top" (default), "bottom", "left", "right" Lato dove impilare
fill "none", "x", "y", "both" Riempimento nello spazio disponibile
expand True/False Espande per occupare spazio extra
padx, pady numero (pixel) Padding esterno orizzontale/verticale
ipadx, ipady numero (pixel) Padding interno orizzontale/verticale
anchor "n", "s", "e", "w", "center", ecc. Posizione nel contenitore

Regole di pack():

  • Di default: side="top", fill="none", expand=False
  • Widget impilati nell'ordine di chiamata a pack()
  • Non si può mescolare con grid() nello stesso contenitore

2. grid() - Layout a Griglia (Professional)

🚫 COPIA BLOCCATA
import tkinter as tk root = tk.Tk() root.title("Esempio grid() - Form di Registrazione") root.geometry("400x300") # Titolo tk.Label(root, text="FORM DI REGISTRAZIONE", font=("Arial", 16, "bold")).grid( row=0, column=0, columnspan=2, pady=20 ) # Nome tk.Label(root, text="Nome:", font=("Arial", 12)).grid(row=1, column=0, sticky="e", padx=10, pady=5) nome_entry = tk.Entry(root, width=25, font=("Arial", 12)) nome_entry.grid(row=1, column=1, padx=10, pady=5) # Cognome tk.Label(root, text="Cognome:", font=("Arial", 12)).grid(row=2, column=0, sticky="e", padx=10, pady=5) cognome_entry = tk.Entry(root, width=25, font=("Arial", 12)) cognome_entry.grid(row=2, column=1, padx=10, pady=5) # Email tk.Label(root, text="Email:", font=("Arial", 12)).grid(row=3, column=0, sticky="e", padx=10, pady=5) email_entry = tk.Entry(root, width=25, font=("Arial", 12)) email_entry.grid(row=3, column=1, padx=10, pady=5) # Password tk.Label(root, text="Password:", font=("Arial", 12)).grid(row=4, column=0, sticky="e", padx=10, pady=5) password_entry = tk.Entry(root, width=25, font=("Arial", 12), show="*") password_entry.grid(row=4, column=1, padx=10, pady=5) # Bottoni frame_bottoni = tk.Frame(root) frame_bottoni.grid(row=5, column=0, columnspan=2, pady=20) tk.Button(frame_bottoni, text="Registrati", width=15, bg="green", fg="white").grid(row=0, column=0, padx=10) tk.Button(frame_bottoni, text="Annulla", width=15, bg="red", fg="white").grid(row=0, column=1, padx=10) # Configurazione pesi colonne (per responsiveness) root.grid_columnconfigure(0, weight=1) root.grid_columnconfigure(1, weight=3) root.mainloop()

📖 Parametri di grid():

Parametro Descrizione Esempio
row, column Posizione nella griglia (0-based) row=0, column=0 (alto-sinistra)
rowspan, columnspan Numero di righe/colonne da occupare columnspan=2 (occupa 2 colonne)
sticky Allineamento nella cella (n,s,e,w,ne,nw,se,sw) sticky="w" (allinea a sinistra)
padx, pady Padding esterno padx=10, pady=5
ipadx, ipady Padding interno ipadx=5, ipady=2

root.grid_columnconfigure(): Configura il comportamento delle colonne.

  • weight=1: La colonna si espande con la finestra
  • weight=0 (default): Dimensione fissa
  • weight=3: Si espande 3 volte più della colonna con weight=1

tk.Frame(): Contenitore per gruppi di widget.

  • Utile per organizzare layout complessi
  • Può avere il suo layout manager
  • Esempio: Raggruppare bottoni in un frame
🎯 CONSIGLIO PROFESSIONALE: Usa grid() per form, dashboard e applicazioni complesse. Usa pack() per layout semplici e rapidi. Evita place() se non necessario (poco responsive).

3. Esempio Pratico: Calcolatrice

🚫 COPIA BLOCCATA
import tkinter as tk class Calcolatrice: def __init__(self, root): self.root = root self.root.title("Calcolatrice") self.root.geometry("300x400") # Variabile per il display self.display_var = tk.StringVar() self.display_var.set("0") # Display (Entry in sola lettura) display = tk.Entry( root, textvariable=self.display_var, font=("Arial", 20), bg="lightgray", fg="black", justify="right", state="readonly", # Solo lettura readonlybackground="lightgray" ) display.grid(row=0, column=0, columnspan=4, sticky="nsew", padx=10, pady=10, ipady=10) # Definizione bottoni: (testo, riga, colonna, colonspan) bottoni = [ ("C", 1, 0, 1), ("←", 1, 1, 1), ("%", 1, 2, 1), ("/", 1, 3, 1), ("7", 2, 0, 1), ("8", 2, 1, 1), ("9", 2, 2, 1), ("*", 2, 3, 1), ("4", 3, 0, 1), ("5", 3, 1, 1), ("6", 3, 2, 1), ("-", 3, 3, 1), ("1", 4, 0, 1), ("2", 4, 1, 1), ("3", 4, 2, 1), ("+", 4, 3, 1), ("0", 5, 0, 2), (".", 5, 2, 1), ("=", 5, 3, 1) ] # Creazione bottoni for (testo, riga, colonna, colonspan) in bottoni: # Colori speciali per operatori if testo in ["C", "←"]: bg_color = "orange" elif testo in ["+", "-", "*", "/", "="]: bg_color = "deepskyblue" else: bg_color = "lightgray" btn = tk.Button( root, text=testo, font=("Arial", 14, "bold"), bg=bg_color, fg="black" if bg_color == "lightgray" else "white", command=lambda t=testo: self.pressione_bottone(t), relief="raised", bd=3 ) btn.grid(row=riga, column=colonna, columnspan=colonspan, sticky="nsew", padx=2, pady=2, ipadx=10, ipady=10) # Configura espansione righe/colonne for i in range(6): root.grid_rowconfigure(i, weight=1) for i in range(4): root.grid_columnconfigure(i, weight=1) def pressione_bottone(self, testo): """Gestisce la pressione dei bottoni""" corrente = self.display_var.get() if testo == "C": # Cancella self.display_var.set("0") elif testo == "←": # Backspace if len(corrente) > 1: self.display_var.set(corrente[:-1]) else: self.display_var.set("0") elif testo == "=": # Calcola try: # Sostituisci simboli per eval espressione = corrente.replace("×", "*").replace("÷", "/") risultato = eval(espressione) self.display_var.set(str(risultato)) except: self.display_var.set("Errore") else: # Aggiungi carattere if corrente == "0" or corrente == "Errore": self.display_var.set(testo) else: self.display_var.set(corrente + testo) # Creazione applicazione root = tk.Tk() app = Calcolatrice(root) root.mainloop()

📖 Concetti Avanzati Introdotti:

tk.StringVar(): Variabile speciale Tkinter.

  • Aggiorna automaticamente i widget quando cambia valore
  • set(): Imposta valore
  • get(): Ottiene valore
  • Altri tipi: IntVar(), DoubleVar(), BooleanVar()

Programmazione a Oggetti (OOP):

  • class Calcolatrice:: Definisce una classe
  • __init__(): Costruttore (inizializza l'oggetto)
  • self: Riferimento all'oggetto stesso
  • Vantaggio: Codice organizzato, riutilizzabile, modulare

eval(): Valuta un'espressione Python.

  • ATTENZIONE: Potenzialmente pericoloso con input utente!
  • Qui è sicuro perché controlliamo l'input
  • In produzione, usare parser matematici sicuri

lambda in command:

  • lambda t=testo: self.pressione_bottone(t)
  • Funzione anonima che "congela" il valore di testo
  • Necessario perché altrimenti tutte le lambda userebbero l'ultimo valore di testo

🏥 ESERCITAZIONE GUIDATA: Sistema Gestione Pazienti

Creeremo un sistema completo per la gestione dei pazienti in una clinica medica.

📋 Scenario Reale - Clinica "Salute & Benessere"

Lavori come sviluppatore per una clinica che ha bisogno di digitalizzare la gestione dei pazienti. L'applicazione deve permettere:

  • Registrazione nuovi pazienti
  • Ricerca pazienti per nome o codice
  • Aggiornamento dati anagrafici
  • Visualizzazione lista pazienti
  • Calcolo statistiche (età media, distribuzione sesso, ecc.)
  • Esportazione dati in CSV

1 Architettura dell'Applicazione

🚫 COPIA BLOCCATA
""" ARCHITETTURA SISTEMA GESTIONE PAZIENTI ======================================= Componenti principali: 1. Database: Lista di dizionari (simula database) 2. GUI: Interfaccia Tkinter con multiple finestre 3. Business Logic: Funzioni per gestire i dati Struttura dati paziente: { "id": "P001", "nome": "Mario", "cognome": "Rossi", "data_nascita": "15/05/1980", "sesso": "M", "indirizzo": "Via Roma 1", "telefono": "3331234567", "email": "mario.rossi@email.com", "gruppo_sanguigno": "A+", "allergie": "Penicillina", "data_registrazione": "10/01/2024" } """

📖 Design Pattern MVC (Model-View-Controller):

  • Model (Modello): I dati (lista pazienti) e la logica di business
  • View (Vista): L'interfaccia grafica (Tkinter)
  • Controller (Controllore): Gestisce l'interazione tra Model e View

Perché usare dizionari?

  • Facili da gestire in Python
  • Struttura dati flessibile
  • Simulano record di database
  • In produzione, si userebbe SQLite o MySQL

2 Classe Paziente e Database

🚫 COPIA BLOCCATA
import tkinter as tk from tkinter import ttk, messagebox import json from datetime import datetime import csv class DatabasePazienti: """Simula un database per i pazienti""" def __init__(self): self.pazienti = [] self.prossimo_id = 1 self.carica_dati() # Carica dati esistenti def genera_id(self): """Genera ID univoco per nuovo paziente""" id_paziente = f"P{self.prossimo_id:03d}" self.prossimo_id += 1 return id_paziente def aggiungi_paziente(self, dati_paziente): """Aggiunge un nuovo paziente al database""" # Aggiungi ID e data registrazione dati_paziente["id"] = self.genera_id() dati_paziente["data_registrazione"] = datetime.now().strftime("%d/%m/%Y %H:%M") self.pazienti.append(dati_paziente) self.salva_dati() return dati_paziente["id"] def cerca_paziente(self, criterio, valore): """Cerca pazienti per criterio (nome, cognome, id)""" risultati = [] valore = valore.lower() for paziente in self.pazienti: if criterio == "id" and valore in paziente.get("id", "").lower(): risultati.append(paziente) elif criterio == "nome" and valore in paziente.get("nome", "").lower(): risultati.append(paziente) elif criterio == "cognome" and valore in paziente.get("cognome", "").lower(): risultati.append(paziente) elif criterio == "telefono" and valore in paziente.get("telefono", ""): risultati.append(paziente) return risultati def aggiorna_paziente(self, id_paziente, nuovi_dati): """Aggiorna i dati di un paziente esistente""" for i, paziente in enumerate(self.pazienti): if paziente["id"] == id_paziente: # Mantieni ID e data registrazione originali nuovi_dati["id"] = id_paziente nuovi_dati["data_registrazione"] = paziente["data_registrazione"] self.pazienti[i] = nuovi_dati self.salva_dati() return True return False def elimina_paziente(self, id_paziente): """Elimina un paziente dal database""" for i, paziente in enumerate(self.pazienti): if paziente["id"] == id_paziente: del self.pazienti[i] self.salva_dati() return True return False def statistiche(self): """Calcola statistiche sui pazienti""" if not self.pazienti: return {} # Calcola età media oggi = datetime.now() eta_totale = 0 conteggio = 0 for paziente in self.pazienti: try: data_nascita = datetime.strptime(paziente["data_nascita"], "%d/%m/%Y") eta = oggi.year - data_nascita.year # Correggi se non ha ancora compiuto gli anni if (oggi.month, oggi.day) < (data_nascita.month, data_nascita.day): eta -= 1 eta_totale += eta conteggio += 1 except: continue # Distribuzione sesso distribuzione_sesso = {"M": 0, "F": 0, "Altro": 0} for paziente in self.pazienti: sesso = paziente.get("sesso", "Altro").upper() if sesso in distribuzione_sesso: distribuzione_sesso[sesso] += 1 else: distribuzione_sesso["Altro"] += 1 # Gruppi sanguigni più comuni gruppi_sanguigni = {} for paziente in self.pazienti: gruppo = paziente.get("gruppo_sanguigno", "Sconosciuto") gruppi_sanguigni[gruppo] = gruppi_sanguigni.get(gruppo, 0) + 1 return { "totale_pazienti": len(self.pazienti), "eta_media": eta_totale / conteggio if conteggio > 0 else 0, "distribuzione_sesso": distribuzione_sesso, "gruppi_sanguigni": gruppi_sanguigni, "ultimo_aggiornamento": datetime.now().strftime("%d/%m/%Y %H:%M") } def esporta_csv(self, filename): """Esporta i pazienti in file CSV""" if not self.pazienti: return False try: with open(filename, 'w', newline='', encoding='utf-8') as file: # Prepara intestazioni intestazioni = self.pazienti[0].keys() writer = csv.DictWriter(file, fieldnames=intestazioni) writer.writeheader() writer.writerows(self.pazienti) return True except Exception as e: print(f"Errore esportazione CSV: {e}") return False def salva_dati(self): """Salva i dati in formato JSON""" try: with open('pazienti.json', 'w', encoding='utf-8') as file: json.dump({ "pazienti": self.pazienti, "prossimo_id": self.prossimo_id }, file, indent=2, ensure_ascii=False) except Exception as e: print(f"Errore salvataggio: {e}") def carica_dati(self): """Carica dati da file JSON""" try: with open('pazienti.json', 'r', encoding='utf-8') as file: dati = json.load(file) self.pazienti = dati.get("pazienti", []) self.prossimo_id = dati.get("prossimo_id", 1) except FileNotFoundError: # File non esiste, inizializza con dati vuoti self.pazienti = [] self.prossimo_id = 1 except Exception as e: print(f"Errore caricamento: {e}") self.pazienti = [] self.prossimo_id = 1

📖 Concetti di Persistenza Dati:

JSON per persistenza:

  • json.dump(): Scrive dati Python in formato JSON
  • json.load(): Legge dati JSON in Python
  • indent=2: Formatta con indentazione (leggibile)
  • ensure_ascii=False: Supporta caratteri non-ASCII

try-except per error handling:

  • try: Prova a eseguire codice che potrebbe fallire
  • except FileNotFoundError: Gestisce file non trovato
  • except Exception as e: Gestisce qualsiasi errore
  • Importante: Previene crash dell'applicazione

datetime per gestione date:

  • datetime.now(): Data/ora corrente
  • strftime(): Formatta data come stringa
  • strptime(): Converte stringa in data
  • %d/%m/%Y: Formato giorno/mese/anno

CSV per esportazione:

  • csv.DictWriter(): Scrive dizionari in CSV
  • writeheader(): Scrive intestazioni colonne
  • writerows(): Scrive tutti i dati
  • Vantaggio: Aprile in Excel, Google Sheets, ecc.

3 Interfaccia Grafica Principale

🚫 COPIA BLOCCATA
class AppGestionePazienti: """Classe principale dell'applicazione""" def __init__(self, root): self.root = root self.root.title("Clinica Salute & Benessere - Gestione Pazienti") self.root.geometry("1200x700") # Inizializza database self.db = DatabasePazienti() # Stile moderno con ttk self.setup_stili() # Crea interfaccia self.setup_interfaccia() # Carica dati iniziali self.aggiorna_lista_pazienti() def setup_stili(self): """Configura stili moderni per l'applicazione""" style = ttk.Style() # Tema moderno style.theme_use('clam') # Stili personalizzati style.configure('Title.TLabel', font=('Arial', 18, 'bold'), foreground='#2c3e50') style.configure('Header.TLabel', font=('Arial', 12, 'bold'), foreground='#34495e') style.configure('Success.TButton', font=('Arial', 10, 'bold'), background='#27ae60', foreground='white') style.configure('Primary.TButton', font=('Arial', 10, 'bold'), background='#3498db', foreground='white') def setup_interfaccia(self): """Crea l'interfaccia grafica principale""" # Frame principale main_frame = ttk.Frame(self.root, padding="10") main_frame.grid(row=0, column=0, sticky="nsew") # Configura espansione self.root.grid_rowconfigure(0, weight=1) self.root.grid_columnconfigure(0, weight=1) main_frame.grid_rowconfigure(1, weight=1) main_frame.grid_columnconfigure(0, weight=1) # Titolo ttk.Label(main_frame, text="🏥 GESTIONE PAZIENTI - CLINICA MEDICA", style='Title.TLabel').grid(row=0, column=0, pady=(0, 20)) # Frame per pulsanti azioni rapide frame_azioni = ttk.Frame(main_frame) frame_azioni.grid(row=1, column=0, sticky="nw", pady=(0, 20)) # Bottoni azioni principali ttk.Button(frame_azioni, text="➕ Nuovo Paziente", style='Success.TButton', command=self.apri_form_nuovo_paziente).grid(row=0, column=0, padx=5) ttk.Button(frame_azioni, text="🔍 Cerca Paziente", style='Primary.TButton', command=self.apri_ricerca).grid(row=0, column=1, padx=5) ttk.Button(frame_azioni, text="📊 Statistiche", command=self.mostra_statistiche).grid(row=0, column=2, padx=5) ttk.Button(frame_azioni, text="💾 Esporta CSV", command=self.esporta_csv).grid(row=0, column=3, padx=5) # Frame per ricerca rapida frame_ricerca_rapida = ttk.LabelFrame(main_frame, text="Ricerca Rapida", padding="10") frame_ricerca_rapida.grid(row=2, column=0, sticky="ew", pady=(0, 20)) ttk.Label(frame_ricerca_rapida, text="Cerca:").grid(row=0, column=0, padx=5) self.entry_ricerca = ttk.Entry(frame_ricerca_rapida, width=40) self.entry_ricerca.grid(row=0, column=1, padx=5) ttk.Button(frame_ricerca_rapida, text="Cerca", command=self.ricerca_rapida).grid(row=0, column=2, padx=5) ttk.Button(frame_ricerca_rapida, text="Pulisci", command=self.pulisci_ricerca).grid(row=0, column=3, padx=5) # Frame per lista pazienti (con Treeview) frame_lista = ttk.LabelFrame(main_frame, text="Elenco Pazienti", padding="10") frame_lista.grid(row=3, column=0, sticky="nsew") # Configura espansione frame lista main_frame.grid_rowconfigure(3, weight=1) frame_lista.grid_rowconfigure(0, weight=1) frame_lista.grid_columnconfigure(0, weight=1) # Treeview (tabella avanzata) colonne = ("ID", "Nome", "Cognome", "Data Nascita", "Sesso", "Telefono", "Gruppo Sanguigno") self.tree = ttk.Treeview(frame_lista, columns=colonne, show="headings", height=15) # Configura intestazioni for col in colonne: self.tree.heading(col, text=col) self.tree.column(col, width=100, minwidth=50) # Scrollbar scrollbar = ttk.Scrollbar(frame_lista, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) # Posiziona Treeview e Scrollbar self.tree.grid(row=0, column=0, sticky="nsew") scrollbar.grid(row=0, column=1, sticky="ns") # Frame per bottoni azioni paziente frame_azioni_paziente = ttk.Frame(frame_lista) frame_azioni_paziente.grid(row=1, column=0, columnspan=2, pady=(10, 0)) ttk.Button(frame_azioni_paziente, text="Visualizza Dettagli", command=self.visualizza_dettagli).grid(row=0, column=0, padx=5) ttk.Button(frame_azioni_paziente, text="Modifica Paziente", command=self.modifica_paziente).grid(row=0, column=1, padx=5) ttk.Button(frame_azioni_paziente, text="Elimina Paziente", command=self.elimina_paziente).grid(row=0, column=2, padx=5) # Status bar self.status_var = tk.StringVar() self.status_var.set(f"Totale pazienti: {len(self.db.pazienti)}") status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief="sunken", anchor="w") status_bar.grid(row=4, column=0, sticky="ew", pady=(10, 0)) # Bind evento selezione Treeview self.tree.bind('<>', self.on_selezione_paziente) self.tree.bind('', lambda e: self.visualizza_dettagli())

📖 Widget Tkinter Avanzati:

ttk.Treeview(): Tabella avanzata.

  • columns=colonne: Definisce le colonne
  • show="headings": Mostra solo intestazioni (no albero)
  • height=15: Numero righe visibili
  • heading(): Configura intestazione colonna
  • column(): Configura dimensione colonna

ttk.Scrollbar(): Barra di scorrimento.

  • orient="vertical": Orientamento verticale
  • command=tree.yview: Connetti al Treeview
  • tree.configure(yscrollcommand=scrollbar.set): Connetti reciprocamente

ttk.Style(): Personalizzazione stili.

  • theme_use('clam'): Tema moderno
  • configure(): Definisce stili personalizzati
  • Style.TLabel: Notazione per stili

Event Binding:

  • bind('<>', funzione): Selezione riga
  • bind('', funzione): Doppio click
  • lambda e: funzione(): Lambda per ignorare parametro evento
📚 CONCETTI IMPARATI IN QUESTO MODULO:
  • Tkinter Basics: Finestre, widget, event handling
  • Layout Managers: pack(), grid(), place()
  • Widget Fondamentali: Label, Button, Entry, Frame
  • Widget Avanzati: Treeview, Scrollbar, ttk.Style
  • OOP con Tkinter: Classi per applicazioni complesse
  • Persistenza Dati: JSON, CSV, datetime
  • Error Handling: try-except per applicazioni robuste
  • Applicazione Reale: Sistema gestione pazienti completo

💪 Esercizi Pratici

Esercizio 1: To-Do List App Facile

Crea un'applicazione per gestire una lista di cose da fare con:

  • Aggiunta nuove attività
  • Eliminazione attività completate
  • Salvataggio automatico in JSON
  • Contatore attività pendenti/completate

Esercizio 2: Convertitore Unità Medio

Crea un convertitore con interfaccia grafica che converta:

  • Temperatura (Celsius, Fahrenheit, Kelvin)
  • Lunghezza (metri, piedi, pollici, miglia)
  • Peso (kg, libbre, once)
  • Valuta (EUR, USD, GBP) con aggiornamento tassi online

Esercizio 3: Gioco Memory Difficile

Crea il gioco Memory (coppie di carte) con:

  • Griglia di carte con immagini/colori
  • Timer e contatore mosse
  • Salvataggio punteggi migliori
  • Multipli livelli di difficoltà

✨ Best Practices per Applicazioni Tkinter

🎯 Organizzazione del Codice

  • Usa classi: Organizza il codice in classi logiche
  • Separa logica e UI: Business logic separata da Tkinter code
  • Usa frames: Raggruppa widget correlati in frames
  • Costanti in alto: Definisci colori, dimensioni, testi all'inizio

🚫 Errori Comuni da Evitare

  • Miscelare pack() e grid(): Non farlo nello stesso container!
  • mainloop() prematuro: Solo dopo aver creato tutta l'UI
  • Widget globali: Usa attributi di classe invece di variabili globali
  • Callback con parentesi: command=funzione non command=funzione()

⚡ Performance e UX

  • Lazy loading: Carica dati pesanti solo quando necessario
  • Threading: Operazioni lunghe in thread separati per non bloccare l'UI
  • Validazione input: Sempre validare input utente prima di elaborare
  • Feedback utente: Mostra status bar, messaggi di conferma, loading indicators
📋 RICORDA PER GLI ESERCIZI:

Tutti gli esercizi di questo modulo vanno eseguiti sul tuo computer, non su Colab. Se incontri errori tipo "no display name" o "$DISPLAY environment variable", significa che stai cercando di eseguire su un ambiente senza display grafico.

✅ Ambienti compatibili:

📝 Riepilogo della Lezione

In questa lezione hai imparato:

🎯 Prossima Lezione: Progetto Finale della Classe Terza! Creerai un'applicazione completa che unisce tutti i concetti imparati: Python base, NumPy, Matplotlib e Tkinter in un unico progetto!