Rozdział 16 · Tworzenie fajnych aplikacji z Tkinter

Mini-Projekt: Edytor Notatek

Dziennik notatek z rozdziału 15 wreszcie dostaje prawdziwe okno i menu.

Bezpośrednia kontynuacja dziennika notatek z rozdziału 15 (15.4) – teraz z prawdziwym oknem, pole wieloliniowe oraz menu „Plik”

Okno edytora notatek z tekstem notatek
Wynik wykonania kodu znajduje się poniżej. Przykład pojawienia się w środowisku kursu. Czcionki, rozmiary i motywy mogą się nieznacznie różnić w zależności od systemu operacyjnego.
redaktor_zametok.py
class NotesEditorApp:
    def __init__(self, root):
        self.root = root
        self.current_path = None
        self.dirty = False
        self.text_widget = ScrolledText(root, wrap="word")
        self.text_widget.pack(fill="both", expand=True)
        self.text_widget.bind("<<Modified>>", self.on_text_modified)
        self.build_menu()
        self.root.protocol("WM_DELETE_WINDOW", self.on_exit)

    def on_text_modified(self, event):
        self.dirty = True
        self.text_widget.edit_modified(False)   # zresetuj wbudowaną flagę Tk

    def confirm_discard_if_dirty(self):
        """True — można kontynuować (New/Open/Exit). False — użytkownik zmienił zdanie."
        if not self.dirty:
            return True
        answer = messagebox.askyesnocancel(
            „Niezapisane zmiany”,
            „Zapisz zmiany przed kontynuowaniem?”,
        )
        if answer is None:   #Cancel – Przerwij bieżącą aktywność
            return False
        if answer:           #Yes – Kontynuuj tylko wtedy, gdy zapis faktycznie się powiódł
            return self.save_as()
        return True          # No — kontynuuj, nie zapisując

    def on_exit(self):
        if self.confirm_discard_if_dirty():
            self.root.destroy()

    def save_as(self):
        """True - Plik został zapisany. False - Użytkownik anulował okno "Zapisz jako".
        filename = filedialog.asksaveasfilename(defaultextension=".txt")
        if not filename:
            return False
        self.current_path = Path(filename)
        self.current_path.write_text(self.text_widget.get("1.0", "end-1c"), encoding="utf-8")
        self.dirty = False
        return True
Trzy wyniki jednego pytania
messagebox.askyesnocancel(...) zwroty True (Tak – zapisz), False (Brak – Nie zapisuj) lub None (Anuluj/zamknij dialog — całkowicie przerwij bieżącą akcję). Normalne askyesno można zrobić tylko dwie pierwsze opcje — do „Wyjścia z niezapisanymi zmianami”
Yes – jeszcze nie został uratowany
Jeśli użytkownik wybrał „Tak”, ale następnie kliknął „Anuluj” w dialogu asksaveasfilename, plik nie został zapisany — i kontynuowanie działania (zamykanie okna, otwieranie innego pliku) jest niemożliwe, w przeciwnym razie niezapisany tekst zostanie utracony. Dlatego confirm_discard_if_dirty() zwraca wynik save_as(), nie True natychmiast po zadzwonieniu „Tak”
Nic fundamentalnie nowego w plikach
filedialog (16.19), Path.read_text/write_text(encoding="utf-8") (Rozdział 15), ScrolledText (gotowa kombinacja Text + Scrollbar) — wszystkie części są już znane, tutaj są zebrane w jednym zastosowaniu.
★★ Zadanie samodzielne
Nowy i Otwórz też pytają

Dodaj new_file() i open_file() metod, z których każda wywołuje confirm_discard_if_dirty() jako pierwsza — i kontynuuje tylko wtedy, gdy zwróciła True.

Praktyka: Edytor notatek
Moduł tkinter otwiera natywne okno Python — wykonaj lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie
Otwórz praktykę →