Python Tkinter 进阶教程

1. 事件绑定与处理

1.1 鼠标事件绑定

import tkinter as tk

def mouse_click(event):
    print(f"鼠标点击位置: ({event.x}, {event.y})")

root = tk.Tk()
frame = tk.Frame(root, width=300, height=200, bg="lightgray")
frame.bind("<Button-1>", mouse_click)  # Button-1是鼠标左键
frame.pack()

root.mainloop()

1.2 键盘事件绑定

import tkinter as tk

def key_press(event):
    print(f"按下了键: {event.char} (键码: {event.keycode})")

root = tk.Tk()
entry = tk.Entry(root)
entry.bind("<Key>", key_press)
entry.pack()

root.mainloop()

1.3 事件修饰符

import tkinter as tk

def ctrl_click(event):
    print("Ctrl+点击组合")

root = tk.Tk()
btn = tk.Button(root, text="按住Ctrl点击我")
btn.bind("<Control-Button-1>", ctrl_click)
btn.pack()

root.mainloop()

2. 使用ttk美化界面

ttk (Themed Tkinter) 提供了更现代的界面组件:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.geometry("300x200")

# 传统Tkinter按钮
tk_btn = tk.Button(root, text="传统按钮")
tk_btn.pack(pady=5)

# ttk按钮
ttk_btn = ttk.Button(root, text="ttk按钮")
ttk_btn.pack(pady=5)

# ttk组合框
combo = ttk.Combobox(root, values=["选项1", "选项2", "选项3"])
combo.pack(pady=5)
combo.current(0)

# ttk进度条
progress = ttk.Progressbar(root, length=200, mode="indeterminate")
progress.pack(pady=10)
progress.start(10)

root.mainloop()

3. 高级布局技巧

3.1 使用Frame组织界面

import tkinter as tk

root = tk.Tk()
root.title("高级布局示例")

# 顶部工具栏
toolbar = tk.Frame(root, bg="lightblue", height=40)
toolbar.pack(fill=tk.X)

btn1 = tk.Button(toolbar, text="文件")
btn1.pack(side=tk.LEFT, padx=5)

btn2 = tk.Button(toolbar, text="编辑")
btn2.pack(side=tk.LEFT, padx=5)

# 左侧导航栏
nav_frame = tk.Frame(root, bg="lightgray", width=100)
nav_frame.pack(side=tk.LEFT, fill=tk.Y)

for i in range(1, 6):
    btn = tk.Button(nav_frame, text=f"选项 {i}")
    btn.pack(fill=tk.X, pady=2)

# 主内容区
content = tk.Frame(root, bg="white")
content.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

text = tk.Text(content)
text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

root.mainloop()

3.2 响应式布局

import tkinter as tk

def on_resize(event):
    # 窗口大小变化时的回调
    print(f"新窗口尺寸: {event.width}x{event.height}")

root = tk.Tk()
root.geometry("400x300")
root.bind("<Configure>", on_resize)

# 使用grid布局并配置权重
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)

text = tk.Text(root)
text.grid(row=0, column=0, sticky="nsew")

# 底部状态栏
status = tk.Label(root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
status.grid(row=1, column=0, sticky="ew")

root.mainloop()

4. 对话框与消息窗口

4.1 标准对话框

import tkinter as tk
from tkinter import messagebox, filedialog, colorchooser

def show_dialogs():
    # 消息对话框
    messagebox.showinfo("信息", "这是一个信息对话框")
    
    # 文件对话框
    filename = filedialog.askopenfilename()
    print("选择的文件:", filename)
    
    # 颜色选择器
    color = colorchooser.askcolor()
    print("选择的颜色:", color)

root = tk.Tk()
btn = tk.Button(root, text="显示对话框", command=show_dialogs)
btn.pack(pady=20)

root.mainloop()

4.2 自定义对话框

import tkinter as tk
from tkinter import simpledialog

class CustomDialog(tk.Toplevel):
    def __init__(self, parent, title="对话框"):
        super().__init__(parent)
        self.title(title)
        self.result = None
        
        tk.Label(self, text="请输入值:").pack(pady=5)
        self.entry = tk.Entry(self)
        self.entry.pack(pady=5)
        
        btn_frame = tk.Frame(self)
        btn_frame.pack(pady=5)
        
        tk.Button(btn_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=5)
        tk.Button(btn_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT)
        
        self.transient(parent)
        self.grab_set()
        parent.wait_window(self)
    
    def on_ok(self):
        self.result = self.entry.get()
        self.destroy()
    
    def on_cancel(self):
        self.destroy()

root = tk.Tk()

def show_custom_dialog():
    dialog = CustomDialog(root, "自定义对话框")
    if dialog.result:
        print("输入的值:", dialog.result)

btn = tk.Button(root, text="自定义对话框", command=show_custom_dialog)
btn.pack(pady=20)

root.mainloop()

5. 菜单与工具栏

5.1 创建菜单系统

import tkinter as tk

def new_file():
    print("新建文件")

def open_file():
    print("打开文件")

def about():
    messagebox.showinfo("关于", "Tkinter菜单示例")

root = tk.Tk()
root.title("菜单示例")

# 创建主菜单栏
menubar = tk.Menu(root)

# 文件菜单
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="新建", command=new_file)
filemenu.add_command(label="打开", command=open_file)
filemenu.add_separator()
filemenu.add_command(label="退出", command=root.quit)
menubar.add_cascade(label="文件", menu=filemenu)

# 帮助菜单
helpmenu = tk.Menu(menubar, tearoff=0)
helpmenu.add_command(label="关于", command=about)
menubar.add_cascade(label="帮助", menu=helpmenu)

root.config(menu=menubar)

# 上下文菜单
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="复制")
context_menu.add_command(label="粘贴")

def show_context_menu(event):
    context_menu.post(event.x_root, event.y_root)

root.bind("<Button-3>", show_context_menu)

root.mainloop()

5.2 创建工具栏

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("工具栏示例")

# 工具栏框架
toolbar = ttk.Frame(root)
toolbar.pack(side=tk.TOP, fill=tk.X)

# 工具栏按钮
icons = ["新建", "打开", "保存", "剪切", "复制", "粘贴"]
for icon in icons:
    btn = ttk.Button(toolbar, text=icon)
    btn.pack(side=tk.LEFT, padx=2, pady=2)

# 添加分隔线
sep = ttk.Separator(toolbar, orient=tk.VERTICAL)
sep.pack(side=tk.LEFT, padx=5, fill=tk.Y)

# 搜索框
search_frame = ttk.Frame(toolbar)
search_frame.pack(side=tk.LEFT, padx=5)

ttk.Label(search_frame, text="搜索:").pack(side=tk.LEFT)
search_entry = ttk.Entry(search_frame, width=20)
search_entry.pack(side=tk.LEFT)

# 主内容区
text = tk.Text(root)
text.pack(fill=tk.BOTH, expand=True)

root.mainloop()

6. 多线程与Tkinter

Tkinter不是线程安全的,需要使用特殊方式处理后台任务:

import tkinter as tk
import threading
import time
import queue

class App:
    def __init__(self, root):
        self.root = root
        self.queue = queue.Queue()
        self.create_widgets()
        self.check_queue()
    
    def create_widgets(self):
        self.label = tk.Label(self.root, text="准备开始")
        self.label.pack(pady=10)
        
        self.btn = tk.Button(self.root, text="开始任务", command=self.start_thread)
        self.btn.pack(pady=5)
        
        self.progress = ttk.Progressbar(self.root, length=200, mode="determinate")
        self.progress.pack(pady=10)
    
    def start_thread(self):
        self.btn.config(state=tk.DISABLED)
        thread = threading.Thread(target=self.long_running_task)
        thread.daemon = True
        thread.start()
    
    def long_running_task(self):
        for i in range(1, 101):
            time.sleep(0.1)  # 模拟耗时操作
            self.queue.put(i)
        self.queue.put("完成")
    
    def check_queue(self):
        try:
            while True:
                msg = self.queue.get_nowait()
                if msg == "完成":
                    self.label.config(text="任务完成!")
                    self.btn.config(state=tk.NORMAL)
                else:
                    self.progress["value"] = msg
                    self.label.config(text=f"处理中... {msg}%")
        except queue.Empty:
            pass
        self.root.after(100, self.check_queue)

root = tk.Tk()
app = App(root)
root.mainloop()

7. 实战:简易文本编辑器

import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog

class TextEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("简易文本编辑器")
        self.filename = None
        
        self.create_menu()
        self.create_toolbar()
        self.create_text_area()
        self.create_statusbar()
        
    def create_menu(self):
        menubar = tk.Menu(self.root)
        
        # 文件菜单
        filemenu = tk.Menu(menubar, tearoff=0)
        filemenu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
        filemenu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
        filemenu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
        filemenu.add_separator()
        filemenu.add_command(label="退出", command=self.root.quit)
        menubar.add_cascade(label="文件", menu=filemenu)
        
        # 编辑菜单
        editmenu = tk.Menu(menubar, tearoff=0)
        editmenu.add_command(label="撤销", command=self.text.edit_undo, accelerator="Ctrl+Z")
        editmenu.add_command(label="重做", command=self.text.edit_redo, accelerator="Ctrl+Y")
        editmenu.add_separator()
        editmenu.add_command(label="剪切", command=self.cut_text, accelerator="Ctrl+X")
        editmenu.add_command(label="复制", command=self.copy_text, accelerator="Ctrl+C")
        editmenu.add_command(label="粘贴", command=self.paste_text, accelerator="Ctrl+V")
        editmenu.add_separator()
        editmenu.add_command(label="查找", command=self.find_text, accelerator="Ctrl+F")
        menubar.add_cascade(label="编辑", menu=editmenu)
        
        self.root.config(menu=menubar)
        
        # 快捷键绑定
        self.root.bind_all("<Control-n>", lambda e: self.new_file())
        self.root.bind_all("<Control-o>", lambda e: self.open_file())
        self.root.bind_all("<Control-s>", lambda e: self.save_file())
        self.root.bind_all("<Control-f>", lambda e: self.find_text())
    
    def create_toolbar(self):
        toolbar = tk.Frame(self.root, bd=1, relief=tk.RAISED)
        
        icons = [
            ("新建", self.new_file),
            ("打开", self.open_file),
            ("保存", self.save_file),
            ("剪切", self.cut_text),
            ("复制", self.copy_text),
            ("粘贴", self.paste_text)
        ]
        
        for text, cmd in icons:
            btn = tk.Button(toolbar, text=text, command=cmd)
            btn.pack(side=tk.LEFT, padx=2, pady=2)
        
        toolbar.pack(side=tk.TOP, fill=tk.X)
    
    def create_text_area(self):
        frame = tk.Frame(self.root)
        scrollbar = tk.Scrollbar(frame)
        self.text = tk.Text(frame, yscrollcommand=scrollbar.set, undo=True)
        scrollbar.config(command=self.text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        frame.pack(fill=tk.BOTH, expand=True)
    
    def create_statusbar(self):
        self.status = tk.Label(self.root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        self.status.pack(side=tk.BOTTOM, fill=tk.X)
    
    def set_status(self, text):
        self.status.config(text=text)
    
    def new_file(self, event=None):
        self.filename = None
        self.text.delete(1.0, tk.END)
        self.set_status("新建文件")
    
    def open_file(self, event=None):
        file = filedialog.askopenfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if file:
            try:
                with open(file, "r") as f:
                    self.text.delete(1.0, tk.END)
                    self.text.insert(1.0, f.read())
                self.filename = file
                self.set_status(f"已打开: {file}")
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件:\n{str(e)}")
    
    def save_file(self, event=None):
        if not self.filename:
            self.filename = filedialog.asksaveasfilename(
                initialfile="未命名.txt",
                defaultextension=".txt",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )
            if not self.filename:
                return
        
        try:
            with open(self.filename, "w") as f:
                f.write(self.text.get(1.0, tk.END))
            self.set_status(f"已保存: {self.filename}")
        except Exception as e:
            messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
    
    def cut_text(self):
        self.text.event_generate("<<Cut>>")
    
    def copy_text(self):
        self.text.event_generate("<<Copy>>")
    
    def paste_text(self):
        self.text.event_generate("<<Paste>>")
    
    def find_text(self, event=None):
        target = simpledialog.askstring("查找", "查找内容:")
        if target:
            start = self.text.search(target, "1.0", stopindex=tk.END)
            if start:
                end = f"{start}+{len(target)}c"
                self.text.tag_add("found", start, end)
                self.text.tag_config("found", background="yellow")
                self.text.mark_set(tk.INSERT, start)
                self.text.see(start)
            else:
                messagebox.showinfo("查找", "未找到匹配内容")

root = tk.Tk()
editor = TextEditor(root)
root.geometry("800x600")
root.mainloop()

通过掌握这些进阶技巧,你将能够开发出功能丰富、界面专业的Tkinter应用程序!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐