tkinter — правило для Cursor: что делает и как установить
Направляет разработку GUI на Python: диктует использование ttk и ttkbootstrap, классовую архитектуру экранов и сетку grid.
.cursor/rules/tkinter.mdcЧто делает tkinter
Правило применяется ко всем файлам **/*.py и запрещает устаревшие виджеты классического tk. Вместо них агент обязан генерировать интерфейсы на tkinter.ttk или ttkbootstrap. Это сразу даёт поддержку стилей вроде flatly или superhero без ручной перерисовки стандартных кнопок.
В коде закрепляется классовая модульная структура с разделением на представления (BaseView) и контроллер AppController. Для позиционирования предписывается использовать grid либо простой pack. Смешивание обоих менеджеров геометрии внутри одного контейнера запрещено, потому что это вызывает TclError. Также блокируются вызовы цепочкой вроде Button().pack(), возвращающие None.
Привязку динамических данных правило требует делать через переменные StringVar или IntVar. Главный поток нельзя блокировать вызовами time.sleep(). Для задержек используется root.after(), а для фоновых задач — потоки или asyncio.
Какие задачи решает
- Собрать современный интерфейс на Python с темами ttkbootstrap
- Разделить экраны десктопного приложения на классовые компоненты
- Избежать ошибок TclError из-за смешивания pack и grid в одном контейнере
- Привязать поля ввода к переменным StringVar и IntVar
- Предотвратить зависание интерфейса из-за блокировок главного потока
Примеры запросов агенту
- Создай окно авторизации на ttkbootstrap с темой superhero и валидацией полей
- Разбей монолитный скрипт на Tkinter на классы BaseView и AppController
- Построй форму настроек через grid с привязкой значений к IntVar
Как установить tkinter
Вручную: положить файлы
.cursor/rules/tkinter.mdcВо frontmatter: description, globs, alwaysApply
Вручную: положить файлы
.claude/rules/tkinter.mdПоле paths во frontmatter ограничивает файлы: globs из источника → paths
AGENTS.md
Codex читает AGENTS.md: вставьте текст правила туда
Вручную: положить файлы
.github/instructions/tkinter.instructions.mdПоле applyTo задаёт, к каким файлам применять: globs из источника → applyTo
Вручную: положить файлы
.devin/rules/tkinter.mdВручную: положить файлы
.clinerules/tkinter.mdПо шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules
Вопросы о tkinter
Как установить правило tkinter в Cursor?
Сохраните файл правила по пути .cursor/rules/tkinter.mdc в корне вашего проекта.
Что делает правило tkinter?
Оно задаёт стандарты архитектуры GUI на Python: требует применять ttk или ttkbootstrap, классовую структуру представлений, аккуратный grid и Tkinter-переменные.
Почему правило запрещает смешивать pack и grid?
Вызов pack() и grid() внутри одного родительского контейнера приводит к конфликту TclError и ломает расчёт геометрии окна.
Исходный текст
tkinter.mdc · CC0-1.0
---
description: Definitive guidelines for building modern, maintainable, and performant Tkinter applications using best practices, ttk, and ttkbootstrap.
globs: **/*.py
---
# tkinter Best Practices
Tkinter is Python's standard GUI toolkit. To build modern, maintainable, and responsive applications in 2025, adhere to these opinionated best practices.
## 1. Modern Theming: `ttk` and `ttkbootstrap`
Always use `tkinter.ttk` widgets for a native look and feel. For a truly modern aesthetic, integrate `ttkbootstrap`.
* **Default to `ttk`**: `ttk` widgets automatically adopt the OS theme.
* **Embrace `ttkbootstrap`**: For a flat, dark-mode-ready, and consistent cross-platform design, `ttkbootstrap` is the go-to. It's a drop-in replacement for `ttk` with enhanced styling.
❌ BAD: Using classic `tk` widgets
```python
import tkinter as tk
root = tk.Tk()
tk.Button(root, text="Old Button").pack()
```
✅ GOOD: Using `ttk` widgets
```python
import tkinter as tk
from tkinter import ttk # Always import ttk
root = tk.Tk()
ttk.Button(root, text="Themed Button").pack()
```
✅ BEST: Using `ttkbootstrap` for modern themes
```python
import ttkbootstrap as ttk # Import ttkbootstrap as ttk
from ttkbootstrap.constants import * # For constants like PRIMARY, INFO
root = ttk.Window(themename="superhero") # Apply a modern theme
ttk.Button(root, text="Bootstrap Button", bootstyle="primary").pack(pady=10)
```
## 2. Code Organization: Class-Based Views
Structure your application with clear separation of concerns. Use classes for each logical view or component to encapsulate widgets and their logic.
* **Main Application Class**: Manages the root window and switches between views.
* **View Classes**: Each class represents a distinct part of the UI (e.g., `LoginView`, `DashboardView`).
* **Thin Bootstrap**: A minimal `main.py` to launch the app.
❌ BAD: Monolithic script
```python
import tkinter as tk
# ... hundreds of lines of mixed UI and logic ...
def create_ui():
root = tk.Tk()
# ...
root.mainloop()
create_ui()
```
✅ GOOD: Class-based structure
```python
import ttkbootstrap as ttk
from typing import Type
class BaseView(ttk.Frame):
def __init__(self, master: ttk.Window, controller: 'AppController') -> None:
super().__init__(master)
self.controller = controller
class MainView(BaseView):
def __init__(self, master: ttk.Window, controller: 'AppController') -> None:
super().__init__(master, controller)
ttk.Label(self, text="Welcome to the App!").pack(pady=20)
ttk.Button(self, text="Go to Settings", command=self.controller.show_settings).pack()
class SettingsView(BaseView):
def __init__(self, master: ttk.Window, controller: 'AppController') -> None:
super().__init__(master, controller)
ttk.Label(self, text="Settings Page").pack(pady=20)
ttk.Button(self, text="Back to Main", command=self.controller.show_main).pack()
class AppController:
def __init__(self, root: ttk.Window) -> None:
self.root = root
self.current_view: BaseView | None = None
self._views: dict[str, Type[BaseView]] = {
"main": MainView,
"settings": SettingsView
}
self.show_main()
def _switch_view(self, view_name: str) -> None:
if self.current_view:
self.current_view.destroy()
ViewClass = self._views[view_name]
self.current_view = ViewClass(self.root, self)
self.current_view.pack(fill=ttk.BOTH, expand=True)
def show_main(self) -> None:
self._switch_view("main")
def show_settings(self) -> None:
self._switch_view("settings")
if __name__ == "__main__":
root = ttk.Window(themename="flatly")
root.title("My Modern Tkinter App")
root.geometry("400x300")
app = AppController(root)
root.mainloop()
```
## 3. Geometry Managers: `grid` is King, `pack` for Simplicity
Choose one geometry manager per parent container and stick to it. `grid` offers the most control and is generally preferred for complex layouts. Use `pack` only for very simple, linear arrangements.
* **Never mix `pack()` and `grid()` in the same parent widget.** This is a common source of layout bugs.
* **Separate widget creation from layout calls.** Chaining `widget.pack()` returns `None`, making the widget unusable later.
❌ BAD: Mixing `pack` and `grid` in the same frame
```python
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
tk.Label(frame, text="Label 1").pack()
tk.Button(frame, text="Button 1").grid(row=0, column=1) # ❌ Will cause TclError
```
❌ BAD: Chaining geometry manager calls
```python
import tkinter as tk
root = tk.Tk()
my_button = tk.Button(root, text="Click Me").pack() # my_button is now None
# my_button.config(text="New Text") # ❌ AttributeError: 'NoneType' object has no attribute 'config'
```
✅ GOOD: Using `grid` consistently
```python
import ttkbootstrap as ttk
root = ttk.Window()
root.title("Grid Layout")
# Configure grid to expand with window
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
main_frame = ttk.Frame(root)
main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
# Configure frame's grid
main_frame.columnconfigure(1, weight=1) # Make column 1 expandable
ttk.Label(main_frame, text="Username:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
username_entry = ttk.Entry(main_frame)
username_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
ttk.Label(main_frame, text="Password:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
password_entry = ttk.Entry(main_frame, show="*")
password_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
ttk.Button(main_frame, text="Login").grid(row=2, column=0, columnspan=2, pady=10)
```
## 4. Data Binding: Use Tkinter Variables
For dynamic UI elements (labels, entries, checkboxes), use `StringVar`, `IntVar`, `BooleanVar`, or `DoubleVar`. These variables notify linked widgets of changes automatically.
❌ BAD: Updating labels with regular Python variables
```python
import tkinter as tk
count = 0
def increment():
global count
count += 1
label.config(text=f"Count: {count}") # Manual update
root = tk.Tk()
label = tk.Label(root, text=f"Count: {count}")
label.pack()
tk.Button(root, text="Increment", command=increment).pack()
```
✅ GOOD: Using `tk.IntVar`
```python
import tkinter as tk
root = tk.Tk()
count_var = tk.IntVar(value=0) # Initialize with a Tkinter variable
def increment():
count_var.set(count_var.get() + 1) # Update the Tkinter variable
label = tk.Label(root, textvariable=count_var) # Link label to variable
label.pack()
tk.Button(root, text="Increment", command=increment).pack()
```
## 5. Responsiveness: Avoid Blocking the Event Loop
Never use `time.sleep()` in a GUI application's main thread. It will freeze the entire UI. For delayed actions, use `root.after()`. For long-running tasks, use threading or `asyncio`.
❌ BAD: Freezing the UI with `time.sleep()`
```python
import tkinter as tk
import time
root = tk.Tk()
status_label = tk.Label(root, text="Ready")
status_label.pack()
def long_task():
status_label.config(text="Working...")
root.update_idletasks() # Force update, but still blocks
time.sleep(3) # ❌ GUI FREEZES for 3 seconds
status_label.config(text="Done!")
tk.Button(root, text="Start Task", command=long_task).pack()
```
✅ GOOD: Non-blocking delays with `root.after()`
```python
import tkinter as tk
root = tk.Tk()
status_label = tk.Label(root, text="Ready")
status_label.pack()
def finish_task():
status_label.config(text="Done!")
def start_task():
status_label.config(text="Working...")
root.after(3000, finish_task) # Schedule finish_task after 3000ms (3 seconds)
tk.Button(root, text="Start Task", command=start_task).pack()
```
## 6. Image Handling: Keep References
Tkinter does not store references to `PhotoImage` objects internally. If your Python variable holding the `PhotoImage` goes out of scope, the image will disappear from the widget.
❌ BAD: Image disappears due to garbage collection
```python
import tkinter as tk
from PIL import Image, ImageTk # Requires Pillow: pip install Pillow
root = tk.Tk()
def create_image_label():
img = Image.open("icon.png") # Assume icon.png exists
photo = ImageTk.PhotoImage(img)
label = tk.Label(root, image=photo) # photo might be garbage collected
label.pack()
create_image_label()
```
✅ GOOD: Explicitly keep a reference to the image
```python
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
img = Image.open("icon.png") # Assume icon.png exists
photo = ImageTk.PhotoImage(img)
label = tk.Label(root, image=photo)
label.image = photo # ✅ Keep a reference directly on the widget
label.pack()
```
## 7. Essential Boilerplate: `root.mainloop()`
Always call `root.mainloop()` as the very last statement in your main application script. Without it, your GUI window will flash and immediately close.
❌ BAD: Window flashes and disappears
```python
import tkinter as tk
root = tk.Tk()
root.title("My App")
tk.Label(root, text="Hello!").pack()
# Forgot root.mainloop()
```
✅ GOOD: Proper application lifecycle
```python
import tkinter as tk
root = tk.Tk()
root.title("My App")
tk.Label(root, text="Hello!").pack()
root.mainloop() # ✅ This is non-negotiable
```
## 8. Robust Callbacks: Handle Exceptions
Wrap potentially failing operations in callbacks with `try...except` blocks. Tkinter often swallows exceptions in event handlers, making debugging difficult. Use `messagebox` to provide user feedback.
❌ BAD: Silent crashes in callbacks
```python
import tkinter as tk
def risky_action():
# This will raise FileNotFoundError if 'nonexistent.txt' doesn't exist
with open("nonexistent.txt", "r") as f:
content = f.read()
print(content) # This line might never be reached
root = tk.Tk()
tk.Button(root, text="Do Risky Action", command=risky_action).pack()
```
✅ GOOD: Graceful error handling
```python
import tkinter as tk
from tkinter import messagebox
def risky_action_safe():
try:
with open("nonexistent.txt", "r") as f:
content = f.read()
messagebox.showinfo("Success", "File read successfully!")
except FileNotFoundError:
messagebox.showerror("Error", "The file 'nonexistent.txt' was not found.")
except Exception as e:
messagebox.showerror("Error", f"An unexpected error occurred: {e}")
root = tk.Tk()
tk.Button(root, text="Do Risky Action Safely", command=risky_action_safe).pack()
```
## 9. Type Hints
Use type hints consistently for improved readability, maintainability, and static analysis. This is standard modern Python practice.
❌ BAD: Untyped functions and methods
```python
class MyWidget(tk.Frame):
def __init__(self, master, data):
super().__init__(master)
self.data = data
def update_label(self, new_text):
# ...
```
✅ GOOD: Typed functions and methods
```python
import tkinter as tk
from typing import Any
class MyWidget(tk.Frame):
def __init__(self, master: tk.Tk | tk.Frame, data: dict[str, Any]) -> None:
super().__init__(master)
self.data = data
self.label = tk.Label(self, text=self.data.get("name", ""))
self.label.pack()
def update_label(self, new_text: str) -> None:
self.label.config(text=new_text)
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc