Загрузка данных
import tkinter as tk
from tkinter import messagebox, simpledialog
import json
import os
from PIL import Image, ImageTk # pip install pillow
# ------------------ Функции работы с файлом заметок ------------------
NOTES_FILE = "notes.json"
def load_notes():
"""Загружает заметки из JSON-файла"""
if not os.path.exists(NOTES_FILE):
return []
try:
with open(NOTES_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except:
return []
def save_notes(notes):
"""Сохраняет заметки в JSON-файл"""
with open(NOTES_FILE, "w", encoding="utf-8") as f:
json.dump(notes, f, ensure_ascii=False, indent=2)
# ------------------ Главный класс приложения ------------------
class NotebookApp:
def __init__(self, root):
self.root = root
self.root.title("~VER$Y - Notebook~")
self.root.geometry("950x600")
# --- Загрузка фонового изображения с помощью Pillow (поддержка PNG/JPG) ---
# Создаём Canvas, на котором будет фон и все виджеты
self.canvas = tk.Canvas(root, highlightthickness=0)
self.canvas.pack(fill="both", expand=True)
self.bg_image = None # будет хранить текущий PhotoImage
self.bg_image_pil = None # оригинальное PIL-изображение для перерисовки
try:
# Открываем изображение через Pillow
self.bg_image_pil = Image.open("background.png") # можно поменять на .jpg
# Первоначальный показ
self.update_background()
# Привязываем функцию изменения размера окна
self.root.bind("<Configure>", self.on_resize)
except Exception as e:
print(f"Фоновое изображение не загружено: {e}")
# Если нет фона, просто заливаем цветом
self.canvas.configure(bg="#f0f0f0")
# Но продолжаем работу – будем размещать виджеты на Canvas
# Иконка окна (если файл существует)
try:
root.iconbitmap(default="MIKU.ico")
except:
pass
# Данные
self.notes = load_notes()
self.current_note_index = None
# Создаём интерфейс
self.create_widgets()
self.update_notes_list()
def update_background(self):
"""Перерисовывает фон под текущий размер окна"""
if self.bg_image_pil is None:
return
# Получаем текущий размер окна
width = self.root.winfo_width()
height = self.root.winfo_height()
if width <= 1 or height <= 1:
width, height = 950, 600 # начальные значения
# Масштабируем изображение до размера окна
resized = self.bg_image_pil.resize((width, height), Image.Resampling.LANCZOS)
self.bg_image = ImageTk.PhotoImage(resized)
# Удаляем старый фон (если есть)
self.canvas.delete("bg_image")
# Рисуем новый фон с тегом "bg_image", чтобы потом легко удалить
self.canvas.create_image(0, 0, image=self.bg_image, anchor="nw", tags="bg_image")
# Опускаем фон на самый нижний уровень
self.canvas.tag_lower("bg_image")
def on_resize(self, event):
"""Срабатывает при изменении размера окна"""
# Избегаем лишних вызовов: обновляем фон только если это корневое окно
if event.widget == self.root:
self.update_background()
# ------------------ Создание виджетов ------------------
def create_widgets(self):
# Левый фрейм для списка заметок (будет размещён на Canvas)
self.left_frame = tk.Frame(self.canvas, bg="#ffffff", bd=2, relief=tk.RAISED)
self.canvas.create_window(10, 10, window=self.left_frame, anchor="nw", width=250, height=580)
tk.Label(self.left_frame, text="Мои заметки", font=("Arial", 14, "bold"), bg="#ffffff").pack(pady=5)
self.notes_listbox = tk.Listbox(self.left_frame, width=30, height=25, font=("Arial", 10))
self.notes_listbox.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
self.notes_listbox.bind("<<ListboxSelect>>", self.on_note_select)
btn_frame = tk.Frame(self.left_frame, bg="#ffffff")
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="Новая", width=8, command=self.new_note).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="Удалить", width=8, command=self.delete_note).pack(side=tk.LEFT, padx=5)
# Правый фрейм для редактирования заметки
self.right_frame = tk.Frame(self.canvas, bg="#ffffff", bd=2, relief=tk.RAISED)
self.canvas.create_window(280, 10, window=self.right_frame, anchor="nw", width=650, height=580)
tk.Label(self.right_frame, text="Заголовок:", font=("Arial", 10), bg="#ffffff").pack(anchor="w", padx=10, pady=(10, 0))
self.title_entry = tk.Entry(self.right_frame, font=("Arial", 12), width=60)
self.title_entry.pack(padx=10, pady=5, fill=tk.X)
tk.Label(self.right_frame, text="Текст заметки:", font=("Arial", 10), bg="#ffffff").pack(anchor="w", padx=10, pady=(10, 0))
self.text_text = tk.Text(self.right_frame, font=("Arial", 11), wrap=tk.WORD, height=20)
self.text_text.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
btn_save = tk.Button(self.right_frame, text="Сохранить заметку", command=self.save_current_note, bg="#4CAF50", fg="white", font=("Arial", 10, "bold"))
btn_save.pack(pady=10)
# Настройка прокрутки для правого фрейма (опционально, если текст большой)
scrollbar = tk.Scrollbar(self.right_frame, command=self.text_text.yview)
self.text_text.config(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 10), pady=5)
# Делаем фреймы "прозрачными" в смысле отсутствия фона поверх Canvas?
# Цвет фона фреймов мы задали явно (белый), но чтобы не было конфликта,
# можно сделать их полупрозрачными? Tkinter не поддерживает прозрачность виджетов.
# Поэтому просто оставляем белый фон – заметки читаются хорошо.
# ------------------ Логика работы с заметками ------------------
def update_notes_list(self):
"""Обновляет список заметок в Listbox"""
self.notes_listbox.delete(0, tk.END)
for note in self.notes:
title = note.get("title", "Без названия")
self.notes_listbox.insert(tk.END, title)
def on_note_select(self, event):
"""Выбор заметки из списка"""
selection = self.notes_listbox.curselection()
if not selection:
return
index = selection[0]
self.current_note_index = index
note = self.notes[index]
# Заполняем поля
self.title_entry.delete(0, tk.END)
self.title_entry.insert(0, note.get("title", ""))
self.text_text.delete(1.0, tk.END)
self.text_text.insert(1.0, note.get("content", ""))
def new_note(self):
"""Создаёт новую заметку"""
# Очищаем поля ввода
self.title_entry.delete(0, tk.END)
self.text_text.delete(1.0, tk.END)
self.current_note_index = None
def save_current_note(self):
"""Сохраняет текущую заметку (новую или редактируемую)"""
title = self.title_entry.get().strip()
content = self.text_text.get(1.0, tk.END).strip()
if not title and not content:
messagebox.showwarning("Пустая заметка", "Невозможно сохранить пустую заметку.")
return
if not title:
title = "Без названия"
if self.current_note_index is not None:
# Редактируем существующую
self.notes[self.current_note_index] = {"title": title, "content": content}
else:
# Новая заметка
self.notes.append({"title": title, "content": content})
save_notes(self.notes)
self.update_notes_list()
# Находим сохранённую заметку в списке и выделяем её
for i, note in enumerate(self.notes):
if note["title"] == title and note["content"] == content:
self.notes_listbox.selection_clear(0, tk.END)
self.notes_listbox.selection_set(i)
self.notes_listbox.see(i)
self.current_note_index = i
break
messagebox.showinfo("Сохранено", f"Заметка \"{title}\" сохранена.")
def delete_note(self):
"""Удаляет выбранную заметку"""
if self.current_note_index is None:
messagebox.showwarning("Нет выбора", "Выберите заметку для удаления.")
return
title = self.notes[self.current_note_index].get("title", "Без названия")
if messagebox.askyesno("Удаление", f"Удалить заметку \"{title}\"?"):
del self.notes[self.current_note_index]
save_notes(self.notes)
self.current_note_index = None
self.title_entry.delete(0, tk.END)
self.text_text.delete(1.0, tk.END)
self.update_notes_list()
# ------------------ Запуск ------------------
if __name__ == "__main__":
root = tk.Tk()
app = NotebookApp(root)
root.mainloop()