Zapisywanie i ładowanie JSON
Zapisuje się nie obrazek, lecz dokument figur — Canvas potrafi odtworzyć z niego płótno w dowolnym momencie.
Canvas nie zapisuje zdjęcia jako pliku – dokument jest zapisywany
Ważne, szczere zastrzeżenie: Canvas nie potrafi zrobić tego samodzielnie
Eksportuj się do zwykłych PNG lub JPEG – niezawodny, wieloplatformowy eksport rastrowy
wymaga dodatkowych zależności i wykracza poza zakres tego rozdziału. Zamiast tego zapisujemy
DOCUMENT to lista liczb, z których Canvas już zbudowany jest
render_document() (Rozdział 18.29):
{
"version": 1,
"canvas": {"background": "#ffffff"},
"items": [
{
"kind": "line",
"coords": [34, 52, 180, 90],
"color": "#2563eb",
"width": 4
},
{
"kind": "rectangle",
"coords": [80, 120, 220, 210],
"color": "#dc2626",
"width": 3
}
]
}
def save_document(self):
path_str = filedialog.asksaveasfilename(
defaultextension=".json", filetypes=[(„Figura JSON”, "*.json")]
)
if not path_str: # użytkownik kliknął "Anuluj" — path_str == ""
return
data = {
"version": 1,
"canvas": {"background": CANVAS_BG},
"items": [shape.to_dict() for shape in self.document],
}
Path(path_str).write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
asksaveasfilename(), i askopenfilename() zwracają pusty łańcuch znaków ""jeśli użytkownik kliknie „Anuluj” None i nie rzucaj wyjątek. Kod musi to zweryfikować PRZED przekazaniem ścieżki do Path(...): Path("") jest ważnym, ale bezsensownym obiektem, który wskazuje na aktualny katalog, a nie na plik wybrany przez użytkownika.A jeśli plik jest uszkodzony?
Użytkownik otwiera plik, co oznacza, że może wybrać dowolne: czyjąś JSON, obraz,
plik niepełny w przypadku niepowodzenia, ręcznie edytowany rysunek z literówką. Rozdział 15
(Rozdział 15.25) już wykazała, że json.load() o tym informuje
via json.JSONDecodeError. Tutaj jest dodawany do
Wiele funkcji: Kluczowe "items" może w ogóle nie istnieć,
kształt może brakować pola, a kolor może być nić, którego Tk nie zna.
Ważne jest, by nie tylko „nie upadać” lokalny lista i zastępuje dokument tylko wtedy, gdy analiza jest całkowicie skuteczna:
def load_from_path(self, path):
try:
data = json.loads(path.read_text(encoding="utf-8"))
shapes = [Shape.from_dict(item) for item in data["items"]]
for shape in shapes:
self.canvas.winfo_rgb(shape.color) # kolor istnieje?
except json.JSONDecodeError as exc:
messagebox.showerror(„Plik nie mógł zostać otwarty”, f"To nie jest poprawna JSON.\n{exc}")
return
except (KeyError, TypeError, ValueError, tk.TclError) as exc:
messagebox.showerror(„Plik nie mógł zostać otwarty”, f"To nie jest zapisany rysunek n{exc}")
return
# Parsowanie zakończyło się sukcesem, tylko teraz zmieniamy stan aplikacji.
self.document = shapes
self.undo_stack.clear()
self.redo_stack.clear()
self.render_document()
self.document = [...] od razu, ale try tylko się trzymać json.loads(). Wtedy plik o nieznanym kolorze zostanie rozłożony, dokument już zostanie zastąpiony — i upadnie render_document(). Podsumowując: stary rysunek zostaje zgubiony, historia cofnięć jest czysta, a każda kolejna próba narysowania czegoś znowu się zawiesza, bo przerysowanie zawsze kończy się tym samym złym kondem. Sprawdzenie PRZED zadaniem kosztuje jedną dodatkową zmienną i oszczędza pracę użytkownika.Shape świadomie nic nie wie o Tkinter (Rozdział 18.29), więc nie może sprawdzić koloru za niego "#2563eb" oraz "не-цвет" są równie podobne do koloru. Ale canvas.winfo_rgb(color) sam pyta Tk i rzuca TclErrorjeśli takiego koloru nie ma. Podział obowiązków zostaje zachowany: model sprawdza strukturę, płótno sprawdza, co należy do rysunku.