Invatare Atomica

Interfete grafice cu Tkinter

Progres lectie:
0%
🎯

Obiectivul lectiei

Vei construi aplicatii grafice Python cu biblioteca Tkinter: ferestre, etichete, campuri de text si butoane, aranjate cu cele trei gestionare de layout (pack, grid, place).

Dupa aceasta lectie vei putea:

  • Sa creezi o fereastra Tkinter si sa o configurezi (titlu, dimensiuni)
  • Sa folosesti widget-urile Label, Entry si Button cu optiunile lor esentiale
  • Sa conectezi un buton la o functie (callback) care citeste din Entry si afiseaza rezultatul
  • Sa aranjezi widget-urile cu pack(), grid() si place() si sa alegi managerul potrivit
  • Sa folosesti StringVar pentru legatura bidirectionala intre date si interfata
  • EXCLUSIV INTENSIV Sa identifici ierarhia de clase Tkinter si s-o compari cu ierarhia C++ Widget/Label/Button

Incearca singur!

Provocare:

Scrie pseudocodul pentru o aplicatie care afiseaza un camp text unde utilizatorul scrie numele, iar la apasarea unui buton saluta utilizatorul cu "Buna ziua, [nume]!"

💡 Ai nevoie de un indiciu?

O interfata grafica = fereastra cu widget-uri (obiecte vizuale).

Fluxul: utilizatorul scrie in Entry → click pe Button → cod apeleaza entry.get() → actualizeaza Label cu label.config(text=...).

1

1. Prima fereastra Tkinter

Tkinter (Tk interface) este biblioteca standard Python pentru interfete grafice. O fereastra Tkinter este un obiect din clasa Tk, iar fiecare element vizual este un widget — obiect dintr-o clasa derivata din Widget.
Structura unui program Tkinter:
  1. Import biblioteca
  2. Creeaza fereastra radacina: root = tk.Tk()
  3. Configureaza fereastra (titlu, dimensiuni)
  4. Adauga widget-uri in fereastra
  5. Porneste bucla de evenimente: root.mainloop()
import tkinter as tk

root = tk.Tk()                      # creaza fereastra principala
root.title("Prima mea fereastra")   # seteaza titlul barei
root.geometry("400x250")            # latime x inaltime in pixeli
root.resizable(False, False)       # blocheaza redimensionarea

# widget-urile se adauga INAINTE de mainloop()

root.mainloop()                    # porneste bucla - ULTIMA linie!
Output real (verificat headless, tkinter 8.6):
isinstance(root, tk.Tk) => True
root.title() => "Prima mea fereastra"
Label.cget('text') => 'Test'   Entry.cget('width') => 20
Tkinter OK: Label, Entry, Button create cu succes
Nota: mainloop() blocheaza executia si asteapta evenimente. Programul ramane activ pana utilizatorul inchide fereastra.
2

2. Widget-urile fundamentale: Label, Entry, Button

Cele trei widget-uri de baza acopera 80% din interfetele simple:
  • Label — afiseaza text sau imagine (read-only)
  • Entry — camp de text pe un singur rand (input)
  • Button — buton cu actiune la click
Toti primesc ca prim argument fereastra parinte.
import tkinter as tk

root = tk.Tk()
root.title("Demo Widgets")
root.geometry("300x200")

# Label -- eticheta cu text
lbl = tk.Label(root, text="Introduceti numele:",
               font=("Arial", 12), fg="navy")
lbl.pack(pady=10)

# Entry -- camp de introducere text
camp_nume = tk.Entry(root, width=25, font=("Arial", 11))
camp_nume.pack(pady=5)

# Label rezultat -- initial gol
lbl_rez = tk.Label(root, text="", font=("Arial", 12), fg="green")
lbl_rez.pack(pady=5)

# Functia callback -- apelata la click pe buton
def saluta():
    nume = camp_nume.get()           # citeste textul din Entry
    if nume.strip():
        lbl_rez.config(text=f"Buna ziua, {nume}!")
    else:
        lbl_rez.config(text="Scrieti un nume mai intai!")

# Button -- command= fara () -- trecem referinta, nu apelul
btn = tk.Button(root, text="Saluta",
               command=saluta,
               bg="#4CAF50", fg="white",
               font=("Arial", 11, "bold"))
btn.pack(pady=10)

root.mainloop()
Comportament verificat (logica testata, tkinter 8.6):
camp_nume.get() cu "Maria" scris => 'Maria'
  label afiseaza: 'Buna ziua, Maria!'
camp gol + click: label afiseaza: 'Scrieti un nume mai intai!'
entry.insert(0, "text initial") => insereaza la pozitia 0
entry.delete(0, END) => entry.get() => ''
label.config(text=...) => modifica textul labelului dinamic
Atentie critica: command=saluta — fara paranteze. Daca scriem command=saluta(), functia se executa imediat la creare, nu la click.
3

3. Gestionarea layout-ului: pack, grid, place

Tkinter ofera trei geometry managers — algoritmi de pozitionare. Nu combinati manageri diferiti in aceeasi fereastra parinte.
pack() — stivuire secventiala (implicit: de sus in jos)
# pack(): parametri frecventi
tk.Label(root, text="Sus").pack(side=tk.TOP,    pady=5)
tk.Label(root, text="Jos").pack(side=tk.BOTTOM, pady=5)
tk.Button(root, text="Stanga").pack(side=tk.LEFT,   padx=5)
tk.Button(root, text="Dreapta").pack(side=tk.RIGHT,  padx=5)
# fill=X -> se intinde pe orizontala
# fill=BOTH + expand=True -> umple tot spatiul
grid() — tabel cu randuri si coloane (ideal pentru formulare)
# grid(): row=rand, column=coloana, indexare de la 0
tk.Label(root, text="Inaltime (cm):").grid(
    row=0, column=0, sticky="e", padx=5, pady=5)
tk.Entry(root, width=10).grid(
    row=0, column=1, padx=5, pady=5)
tk.Label(root, text="Greutate (kg):").grid(
    row=1, column=0, sticky="e", padx=5, pady=5)
tk.Entry(root, width=10).grid(
    row=1, column=1, padx=5, pady=5)
tk.Button(root, text="Calculeaza").grid(
    row=2, column=0, columnspan=2, pady=10)
# sticky="e"    -> aliniere la dreapta in celula
# columnspan=2  -> celula se intinde pe 2 coloane
place() — coordonate absolute sau relative
# place(): pozitionare exacta in pixeli
tk.Button(root, text="Absolut").place(
    x=50, y=80, width=100, height=40)
# sau relative (0.0=stanga/sus, 1.0=dreapta/jos)
tk.Label(root, text="Centru").place(
    relx=0.5, rely=0.5, anchor="center")
# place() nu e recomandat pt UI redimensionabile
Comportament verificat (headless, tkinter 8.6):
pack(side=LEFT, padx=5)  -> 3 butoane aliniate orizontal
pack(pady=10)            -> spatiu 10 pixeli sus si jos
grid(row=0, column=0)   -> pozitionare in tabel
grid(sticky="e")        -> aliniere la dreapta in celula
grid(columnspan=2)      -> celula pe 2 coloane
place(x=50, y=80)       -> coordonate absolute in pixeli
place(relx=0.5, rely=0.5, anchor="center") -> centrat relativ
4

4. StringVar — variabile reactive

StringVar (si IntVar, DoubleVar) sunt variabile speciale Tkinter care notifica automat widget-urile legate prin textvariable= atunci cand valoarea se schimba. Implementeaza pattern-ul Observer.
import tkinter as tk

root = tk.Tk()
root.title("StringVar Demo")
root.geometry("350x180")

# Variabila reactiva
mesaj_var = tk.StringVar()
mesaj_var.set("Astept input...")

# Label legat la variabila -- se actualizeaza automat
lbl_live = tk.Label(root, textvariable=mesaj_var,
                     font=("Arial", 13), fg="darkblue")
lbl_live.pack(pady=15)

entry_var = tk.StringVar()
camp = tk.Entry(root, textvariable=entry_var,
              font=("Arial", 11), width=25)
camp.pack(pady=5)

def actualizeaza():
    text = entry_var.get()
    mesaj_var.set(f"Ai scris: '{text}'")
    # Label se actualizeaza AUTOMAT -- fara .config() explicit

tk.Button(root, text="Actualizeaza",
         command=actualizeaza).pack(pady=8)
root.mainloop()
Comportament verificat (headless, tkinter 8.6):
sv = StringVar(); sv.set('test'); sv.get() => 'test'
sv.set('altceva');  sv.get() => 'altceva'
iv = IntVar();  iv.set(42);  iv.get() => 42

Label(textvariable=sv) se actualizeaza automat
cand sv.set('actualizat automat') este apelat
-- fara apel explicit la .config().
Cand folosesti StringVar? Cand acelasi text trebuie afisat in mai multe locuri simultan, sau cand vrei o traca (trace) — o functie apelata automat la fiecare modificare a valorii.
5

5. Aplicatie completa: Calculator IMC

Construim o aplicatie functionala care calculeaza Indicele de Masa Corporala. Folosim grid() pentru formular si separare clara intre logica si interfata.
import tkinter as tk

# --- Logica business ---
def calculeaza_imc(h_str, g_str):
    try:
        h = float(h_str) / 100
        g = float(g_str)
        if h <= 0 or g <= 0: return "Valori invalide!"
        imc = g / (h ** 2)
        if   imc < 18.5: status = "Subponderal"
        elif imc < 25:   status = "Normal"
        elif imc < 30:   status = "Supraponderal"
        else:            status = "Obez"
        return f"IMC = {imc:.2f} ({status})"
    except ValueError:
        return "Introduceti numere valide!"

root = tk.Tk()
root.title("Calculator IMC")
root.geometry("320x220")
root.resizable(False, False)

tk.Label(root, text="Inaltime (cm):").grid(row=0, column=0, sticky="e", padx=10, pady=8)
entry_h = tk.Entry(root, width=12)
entry_h.grid(row=0, column=1, padx=10, pady=8)

tk.Label(root, text="Greutate (kg):").grid(row=1, column=0, sticky="e", padx=10, pady=8)
entry_g = tk.Entry(root, width=12)
entry_g.grid(row=1, column=1, padx=10, pady=8)

def pe_click():
    rez = calculeaza_imc(entry_h.get(), entry_g.get())
    lbl_rez.config(text=rez)

tk.Button(root, text="Calculeaza IMC", command=pe_click,
         bg="#2196F3", fg="white").grid(row=2, column=0, columnspan=2, pady=12)

lbl_rez = tk.Label(root, text="---", font=("Arial", 13, "bold"), fg="darkgreen")
lbl_rez.grid(row=3, column=0, columnspan=2, pady=8)

root.mainloop()
Output real -- logica verificata (python, headless):
calculeaza_imc(170, 65)  =>  IMC = 22.49 (Normal)
calculeaza_imc(175, 90)  =>  IMC = 29.39 (Supraponderal)
calculeaza_imc(160, 45)  =>  IMC = 17.58 (Subponderal)
calculeaza_imc(abc, 65)  =>  Introduceti numere valide!
Principiu de design: Logica este separata de interfata (Separation of Concerns).
6

6. OOP in Tkinter si echivalentul C++ EXCLUSIV INTENSIV

⚡ Sectiune doar pentru intensiv informatica

Tkinter este implementat complet in paradigma OOP. Intelegerea ierarhiei de clase te ajuta sa extinzi widget-urile si sa compari cu modelul C++.

Ierarhia de clase Tkinter (simplificata):
  • objectBaseWidgetWidgetLabel, Button, Entry ...
  • Fiecare widget mosteneste: pack(), grid(), place(), config(), destroy()
  • Fiecare suprascrie constructorul cu parametrii specifici
import tkinter as tk

class LabelColorat(tk.Label):
    def __init__(self, parinte, text, tip="info", **kwargs):
        culori = {"info": "#dbeafe", "ok": "#dcfce7", "eroare": "#fee2e2"}
        bg = culori.get(tip, "white")
        super().__init__(parinte, text=text, bg=bg, font=("Arial", 11), padx=8, pady=4, **kwargs)

root = tk.Tk()
LabelColorat(root, "Operatie reusita!", tip="ok").pack(pady=5)
LabelColorat(root, "Eroare de calcul!", tip="eroare").pack(pady=5)
root.mainloop()
Echivalent conceptual in C++ (compilat g++ -std=c++17):
#include <iostream>
#include <string>
using namespace std;

// Clasa de baza -- echivalent tk.Widget
class Widget {
protected:
    string text;
public:
    Widget(string t) : text(t) {}
    virtual void show() const { cout << "Widget: " << text << endl; }
};

// Clasa derivata -- echivalent tk.Label
class Label : public Widget {
public:
    Label(string t) : Widget(t) {}
    void show() const override { cout << "Label: [" << text << "]" << endl; }
};

// Clasa derivata -- echivalent tk.Button
class Button : public Widget {
public:
    Button(string t) : Widget(t) {}
    void click() const { cout << "Click: " << text << endl; }
    void show() const override { cout << "Button: [" << text << "]" << endl; }
};

int main() {
    Label  l("Salut, GUI!");
    Button b("Calculeaza IMC");
    l.show();
    b.show();
    b.click();
    return 0;
}
Output real C++ (g++ -std=c++17, rulat pe Windows):
Label: [Salut, GUI!]
Button: [Calculeaza IMC]
Click: Calculeaza IMC
Comparatie Python vs C++ pentru OOP GUI:
ConceptPython TkinterC++
Clasa de bazatk.Widgetclass Widget
Mostenireclass X(tk.Widget)class X : public Widget
Suprascrieredef show(self):void show() const override
Constructordef __init__(self,...)Widget(string t):text(t){}
Metoda virtualaimplicit (toate metodele)explicit: virtual

Exercitii practice

Exercitiul 1 (Nivel minim) — Fereastra de salut

Scrie o aplicatie Tkinter cu un Label "Numele tau:", un Entry si un Button "Saluta" care afiseaza "Buna ziua, [nume]!" intr-un al doilea Label.

Foloseste pack() pentru layout. Testeaza cu campul gol.

Exercitiul 2 (Nivel standard) — Convertor de temperaturi

Construieste o aplicatie care converteste Celsius ↔ Fahrenheit cu doua campuri Entry si doua butoane "C → F" / "F → C".

Formula: F = C × 9/5 + 32 si C = (F − 32) × 5/9. Foloseste grid(). Trateaza inputul invalid cu try/except ValueError.

Verificare: 100°C → 212°F  |  32°F → 0°C  |  −40°C → −40°F

Exercitiul 3 (Nivel performanta) — Mini-agenda cu fisiere

Extinde calculatorul IMC cu: camp pentru numele pacientului, buton "Salveaza" care scrie in imc_log.txt formatul [data] Pacient: [Nume] | IMC = X.XX (Categorie), buton "Istoric" care citeste si afiseaza ultimele 5 intrari intr-un widget tk.Text.

EXCLUSIV INTENSIV Refactorizeaza aplicatia ca o clasa Python care mosteneste din tk.Frame.

Ce ai invatat astazi

  • Structura unui program Tkinter: Tk() → widgets → mainloop()
  • Widget-urile fundamentale: Label (afisare), Entry (input), Button (actiune)
  • Conectarea unui buton la o functie: command=functie — fara paranteze
  • Citirea inputului: entry.get() si actualizarea afisajului: label.config(text=...)
  • Cele 3 geometry managers: pack() (stivuire), grid() (tabel), place() (absolut/relativ)
  • StringVar ca variabila reactiva: widget-urile legate se actualizeaza automat
  • EXCLUSIV INTENSIV Ierarhia OOP Tkinter si echivalentul C++ cu virtual/override

Urmatoarea lectie

In lectia 6 vei integra OOP, fisiere si GUI intr-un mini-proiect complet.

Continua →