我的个人博客

Tkinter GUI 编程指南

返回首页
技术分享 2025-12-20

基础入门

导入库

import tkinter as tk
# 或者
from tkinter import *

创建基本窗口

import tkinter as tk

# 创建主窗口
root = tk.Tk()
root.title("我的第一个GUI程序")
root.geometry("400x300")  # 宽度x高度

# 开启主循环
root.mainloop()

窗口属性设置

基本属性

import tkinter as tk

root = tk.Tk()

# 窗口标题
root.title("窗口标题")

# 窗口大小和位置
root.geometry("800x600+100+100")  # 宽x高+左边距+上边距

# 最小/最大尺寸
root.minsize(300, 200)
root.maxsize(1200, 800)

# 窗口是否可缩放
root.resizable(True, True)  # (宽可缩放, 高可缩放)
root.resizable(False, False)  # 固定大小

# 窗口图标
root.iconbitmap("icon.ico")  # Windows
# root.iconphoto(True, tk.PhotoImage(file="icon.png"))  # 跨平台

# 背景颜色
root.configure(bg="lightblue")

# 窗口透明度 (0.0-1.0)
root.attributes("-alpha", 0.9)

# 窗口置顶
root.attributes("-topmost", True)

# 窗口关闭前执行
def on_closing():
    if messagebox.askokcancel("退出", "确定要退出吗?"):
        root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)

root.mainloop()

常用组件

Label 标签组件

# 基本标签
label1 = tk.Label(root, text="Hello, Tkinter!")
label1.pack()

# 带样式的标签
label2 = tk.Label(
    root,
    text="样式化标签",
    font=("Arial", 16, "bold"),
    fg="white",           # 前景色(文字颜色)
    bg="blue",            # 背景色
    padx=10,              # 水平内边距
    pady=5,               # 垂直内边距
    relief=tk.RAISED,     # 边框样式
    bd=2                  # 边框宽度
)
label2.pack(pady=10)

# 图片标签
photo = tk.PhotoImage(file="image.png")
image_label = tk.Label(root, image=photo)
image_label.pack()

Entry 输入框组件

# 基本输入框
entry1 = tk.Entry(root)
entry1.pack(pady=5)

# 带样式的输入框
entry2 = tk.Entry(
    root,
    font=("Arial", 12),
    width=30,
    bg="lightyellow",
    fg="black",
    show="*",              # 显示字符(密码框用*)
    state="normal"         # normal, readonly, disabled
)
entry2.pack(pady=5)

# 字符串变量绑定
var_text = tk.StringVar(value="默认文本")
entry3 = tk.Entry(root, textvariable=var_text)
entry3.pack()

# 获取输入内容
def get_input():
    content = entry1.get()
    print(f"输入内容: {content}")

# 设置输入内容
def set_input():
    entry1.delete(0, tk.END)  # 清空
    entry1.insert(0, "新内容")  # 插入

Button 按钮组件

# 基本按钮
button1 = tk.Button(root, text="点击我")
button1.pack(pady=5)

# 带命令的按钮
def on_click():
    print("按钮被点击了!")

button2 = tk.Button(
    root,
    text="提交",
    command=on_click,
    font=("Arial", 12),
    bg="green",
    fg="white",
    width=10,
    height=2,
    relief=tk.RAISED,
    bd=3
)
button2.pack(pady=5)

# 带参数的按钮命令
def show_message(message):
    messagebox.showinfo("提示", message)

button3 = tk.Button(
    root,
    text="显示消息",
    command=lambda: show_message("Hello World!")
)
button3.pack(pady=5)

Text 文本框组件

# 创建文本框
text_widget = tk.Text(root, height=10, width=50)
text_widget.pack(pady=10)

# 插入文本
text_widget.insert(tk.END, "这是文本框内容\n")
text_widget.insert(tk.INSERT, "在光标位置插入")

# 获取所有文本
all_text = text_widget.get(1.0, tk.END)

# 获取选中文本
selected_text = text_widget.get(tk.SEL_FIRST, tk.SEL_LAST)

# 清空文本框
text_widget.delete(1.0, tk.END)

# 添加滚动条
scrollbar = tk.Scrollbar(root)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

text_widget.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=text_widget.yview)

布局管理

pack 布局

# 基本用法
widget.pack()

# 带参数的pack
widget.pack(
    side=tk.TOP,         # TOP, BOTTOM, LEFT, RIGHT
    fill=tk.BOTH,        # NONE, X, Y, BOTH
    expand=True,         # 是否扩展
    padx=10,             # 水平外边距
    pady=5,              # 垂直外边距
    ipadx=5,             # 水平内边距
    ipady=2              # 垂直内边距
)

# 示例
label1 = tk.Label(root, text="顶部", bg="red")
label1.pack(side=tk.TOP, fill=tk.X)

label2 = tk.Label(root, text="左侧", bg="green")
label2.pack(side=tk.LEFT, fill=tk.Y)

label3 = tk.Label(root, text="中间", bg="blue")
label3.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

grid 布局

# 基本网格布局
label1 = tk.Label(root, text="用户名:")
label1.grid(row=0, column=0, padx=5, pady=5)

entry1 = tk.Entry(root)
entry1.grid(row=0, column=1, padx=5, pady=5)

label2 = tk.Label(root, text="密码:")
label2.grid(row=1, column=0, padx=5, pady=5)

entry2 = tk.Entry(root, show="*")
entry2.grid(row=1, column=1, padx=5, pady=5)

# 跨行跨列
button = tk.Button(root, text="登录")
button.grid(row=2, column=0, columnspan=2, pady=10)

# 粘性选项
widget.grid(
    row=0,
    column=0,
    sticky="nsew",      # 北南东西(上下左右)
    padx=5,
    pady=5,
    ipadx=2,
    ipady=2
)

# 配置行列权重
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)

place 布局

# 绝对定位
label = tk.Label(root, text="绝对定位")
label.place(x=50, y=50)

# 相对定位
button = tk.Button(root, text="相对定位")
button.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

# 尺寸控制
widget.place(
    x=100, y=100,
    width=200, height=50,
    relx=0.1, rely=0.1,
    relwidth=0.8, relheight=0.2
)

高级组件

Listbox 列表框

# 创建列表框
listbox = tk.Listbox(root, height=6)
listbox.pack(pady=10)

# 添加项目
items = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"]
for item in items:
    listbox.insert(tk.END, item)

# 获取选中项
def get_selection():
    selection = listbox.curselection()
    if selection:
        index = selection[0]
        item = listbox.get(index)
        print(f"选中: {item}")

# 删除选中项
def delete_selection():
    selection = listbox.curselection()
    if selection:
        listbox.delete(selection[0])

# 绑定事件
listbox.bind('<<ListboxSelect>>', lambda e: get_selection())

Combobox 下拉框

from tkinter import ttk

# 创建下拉框
combo = ttk.Combobox(root, values=["选项1", "选项2", "选项3"])
combo.pack(pady=10)

# 设置默认值
combo.set("选项1")

# 获取选中值
def get_combo_value():
    selected = combo.get()
    print(f"选中: {selected}")

# 只读模式
combo['state'] = 'readonly'  # normal, readonly, disabled

Scale 滑块组件

# 创建滑块
scale = tk.Scale(
    root,
    from_=0,
    to=100,
    orient=tk.HORIZONTAL,    # HORIZONTAL, VERTICAL
    length=200,
    label="音量",
    command=lambda value: print(f"音量: {value}")
)
scale.pack(pady=10)

# 获取滑块值
def get_scale_value():
    value = scale.get()
    print(f"当前值: {value}")

Checkbutton 复选框

# 变量绑定
var1 = tk.IntVar()
var2 = tk.IntVar()

check1 = tk.Checkbutton(
    root,
    text="选项1",
    variable=var1,
    command=lambda: print(f"选项1: {var1.get()}")
)
check1.pack()

check2 = tk.Checkbutton(
    root,
    text="选项2",
    variable=var2
)
check2.pack()

# 检查状态
def check_states():
    print(f"选项1: {bool(var1.get())}")
    print(f"选项2: {bool(var2.get())}")

Radiobutton 单选框

# 变量绑定
var_radio = tk.StringVar(value="选项1")

radio1 = tk.Radiobutton(
    root,
    text="选项1",
    variable=var_radio,
    value="选项1",
    command=lambda: print(f"选中: {var_radio.get()}")
)
radio1.pack()

radio2 = tk.Radiobutton(
    root,
    text="选项2",
    variable=var_radio,
    value="选项2"
)
radio2.pack()

radio3 = tk.Radiobutton(
    root,
    text="选项3",
    variable=var_radio,
    value="选项3"
)
radio3.pack()

菜单栏

创建菜单

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

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

# 编辑菜单
edit_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="编辑", menu=edit_menu)
edit_menu.add_command(label="复制", command=lambda: print("复制"))
edit_menu.add_command(label="粘贴", command=lambda: print("粘贴"))

# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
help_menu.add_command(label="关于", command=lambda: messagebox.showinfo("关于", "Tkinter程序"))

右键菜单

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

# 创建右键菜单
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="剪切", command=lambda: print("剪切"))
context_menu.add_command(label="复制", command=lambda: print("复制"))
context_menu.add_command(label="粘贴", command=lambda: print("粘贴"))

# 绑定右键事件
root.bind("<Button-3>", show_context_menu)

对话框

消息框

from tkinter import messagebox

# 信息框
messagebox.showinfo("提示", "这是一个信息提示")

# 警告框
messagebox.showwarning("警告", "这是一个警告信息")

# 错误框
messagebox.showerror("错误", "发生了一个错误")

# 询问框
result = messagebox.askokcancel("确认", "确定要删除吗?")
if result:
    print("用户点击了确定")

# 是/否框
result = messagebox.askyesno("选择", "是否继续?")
if result:
    print("用户选择了是")

# 重试/取消框
result = messagebox.askretrycancel("重试", "操作失败,是否重试?")

文件对话框

from tkinter import filedialog

# 打开文件对话框
file_path = filedialog.askopenfilename(
    title="选择文件",
    filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)

# 保存文件对话框
save_path = filedialog.asksaveasfilename(
    title="保存文件",
    defaultextension=".txt",
    filetypes=[("文本文件", "*.txt")]
)

# 选择目录
directory = filedialog.askdirectory(title="选择目录")

颜色选择器

from tkinter import colorchooser

# 选择颜色
color = colorchooser.askcolor(title="选择颜色")
if color[1]:  # color[1] 是十六进制颜色值
    selected_color = color[1]
    print(f"选择的颜色: {selected_color}")

事件处理

绑定事件

# 按键事件
def on_key_press(event):
    print(f"按键: {event.keysym}")

root.bind("<KeyPress>", on_key_press)
root.bind("<Return>", lambda e: print("回车键"))
root.bind("<Escape>", lambda e: root.quit())

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

def on_mouse_motion(event):
    print(f"鼠标移动: ({event.x}, {event.y})")

button.bind("<Button-1>", on_mouse_click)  # 左键点击
button.bind("<B1-Motion>", on_mouse_motion)  # 左键拖动
button.bind("<Enter>", lambda e: print("鼠标进入"))
button.bind("<Leave>", lambda e: print("鼠标离开"))

# 窗口事件
def on_window_resize(event):
    print(f"窗口大小: {event.width}x{event.height}")

root.bind("<Configure>", on_window_resize)

事件参数

def event_info(event):
    print(f"事件类型: {event.type}")
    print(f"组件: {event.widget}")
    print(f"时间: {event.time}")
    print(f"坐标: ({event.x}, {event.y})")
    print(f"按键: {event.keysym}")
    print(f"状态: {event.state}")

# 绑定多个事件
widget.bind("<Button-1>", event_info)
widget.bind("<KeyPress>", event_info)

多窗口管理

Toplevel 顶层窗口

def open_new_window():
    # 创建新窗口
    new_window = tk.Toplevel(root)
    new_window.title("新窗口")
    new_window.geometry("300x200")
    
    # 在新窗口中添加组件
    label = tk.Label(new_window, text="这是一个新窗口")
    label.pack(pady=20)
    
    close_button = tk.Button(
        new_window,
        text="关闭窗口",
        command=new_window.destroy
    )
    close_button.pack(pady=10)

# 主窗口中的按钮
open_btn = tk.Button(root, text="打开新窗口", command=open_new_window)
open_btn.pack(pady=20)

窗口间通信

class MainWindow:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("主窗口")
        
        self.data = "共享数据"
        
        tk.Button(self.root, text="打开子窗口", command=self.open_child).pack()
    
    def open_child(self):
        ChildWindow(self)
    
    def update_data(self, new_data):
        self.data = new_data
        print(f"数据已更新: {self.data}")

class ChildWindow:
    def __init__(self, parent):
        self.parent = parent
        
        self.window = tk.Toplevel(parent.root)
        self.window.title("子窗口")
        
        tk.Label(self.window, text=f"当前数据: {parent.data}").pack()
        
        entry = tk.Entry(self.window)
        entry.pack()
        
        tk.Button(
            self.window,
            text="更新数据",
            command=lambda: self.update_data(entry.get())
        ).pack()
    
    def update_data(self, new_data):
        self.parent.update_data(new_data)
        self.window.destroy()

app = MainWindow()
app.root.mainloop()

样式美化

ttk 主题

import tkinter.ttk as ttk

# 获取可用主题
print(ttk.Style().theme_names())

# 设置主题
style = ttk.Style()
style.theme_use("clam")  # clam, alt, default, classic

# 自定义样式
style.configure("Custom.TButton",
    font=("Arial", 12),
    foreground="white",
    background="blue"
)

# 使用自定义样式
button = ttk.Button(root, text="自定义按钮", style="Custom.TButton")
button.pack(pady=10)

颜色和字体

# 颜色定义
colors = {
    'bg': '#f0f0f0',      # 背景色
    'fg': '#333333',      # 前景色
    'button_bg': '#4CAF50', # 按钮背景
    'button_fg': 'white',   # 按钮前景
    'accent': '#2196F3'     # 强调色
}

# 字体定义
fonts = {
    'title': ('Arial', 16, 'bold'),
    'normal': ('Arial', 10),
    'button': ('Arial', 10, 'bold')
}

# 应用样式
title_label = tk.Label(
    root,
    text="标题",
    font=fonts['title'],
    fg=colors['fg'],
    bg=colors['bg']
)

边框和效果

# 边框样式
relief_options = [tk.FLAT, tk.RAISED, tk.SUNKEN, tk.GROOVE, tk.RIDGE]

for i, relief in enumerate(relief_options):
    label = tk.Label(
        root,
        text=relief,
        relief=relief,
        bd=3,  # 边框宽度
        padx=10,
        pady=5
    )
    label.grid(row=i, column=0, pady=5)

# 创建3D效果
frame = tk.Frame(root, relief=tk.RAISED, bd=2)
frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

content = tk.Label(frame, text="3D边框效果", bg="white")
content.pack(padx=5, pady=5)

实用示例

计算器程序

class Calculator:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("计算器")
        self.root.geometry("300x400")
        self.root.resizable(False, False)
        
        self.current = ""
        self.result_var = tk.StringVar(value="0")
        
        self.create_widgets()
    
    def create_widgets(self):
        # 显示屏
        display = tk.Entry(
            self.root,
            textvariable=self.result_var,
            font=('Arial', 20),
            justify='right',
            bd=10,
            readonlybackground='white',
            state='readonly'
        )
        display.pack(fill=tk.BOTH, padx=10, pady=10)
        
        # 按钮布局
        buttons = [
            ['C', '±', '%', '÷'],
            ['7', '8', '9', '×'],
            ['4', '5', '6', '-'],
            ['1', '2', '3', '+'],
            ['0', '.', '=']
        ]
        
        for row_idx, row in enumerate(buttons):
            for col_idx, text in enumerate(row):
                if text == '0':
                    btn = tk.Button(
                        self.root, text=text,
                        font=('Arial', 14), height=2,
                        command=lambda t=text: self.on_click(t)
                    )
                    btn.grid(row=row_idx+1, column=col_idx, 
                            columnspan=2, sticky='nsew', padx=2, pady=2)
                elif text == '=':
                    btn = tk.Button(
                        self.root, text=text,
                        font=('Arial', 14), height=2,
                        bg='orange', fg='white',
                        command=lambda t=text: self.on_click(t)
                    )
                    btn.grid(row=row_idx+1, column=col_idx, 
                            columnspan=2, sticky='nsew', padx=2, pady=2)
                else:
                    color = 'lightgray' if text in 'C±%÷×+-' else 'white'
                    btn = tk.Button(
                        self.root, text=text,
                        font=('Arial', 14), height=2,
                        bg=color,
                        command=lambda t=text: self.on_click(t)
                    )
                    btn.grid(row=row_idx+1, column=col_idx, 
                            sticky='nsew', padx=2, pady=2)
        
        # 配置网格权重
        for i in range(5):
            self.root.grid_rowconfigure(i+1, weight=1)
        for i in range(4):
            self.root.grid_columnconfigure(i, weight=1)
    
    def on_click(self, text):
        if text == 'C':
            self.current = ""
            self.result_var.set("0")
        elif text == '=':
            try:
                # 替换运算符
                expression = self.current.replace('×', '*').replace('÷', '/')
                result = eval(expression)
                self.result_var.set(str(result))
                self.current = str(result)
            except:
                self.result_var.set("错误")
                self.current = ""
        elif text == '±':
            if self.current and self.current != '0':
                if self.current[0] == '-':
                    self.current = self.current[1:]
                else:
                    self.current = '-' + self.current
                self.result_var.set(self.current)
        else:
            if self.result_var.get() == "0" or self.result_var.get() == "错误":
                self.current = text
            else:
                self.current += text
            self.result_var.set(self.current)
    
    def run(self):
        self.root.mainloop()

# 运行计算器
if __name__ == "__main__":
    calc = Calculator()
    calc.run()

待办事项应用

class TodoApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("待办事项")
        self.root.geometry("400x500")
        
        self.todos = []
        self.create_widgets()
    
    def create_widgets(self):
        # 标题
        title = tk.Label(
            self.root,
            text="我的待办事项",
            font=('Arial', 16, 'bold'),
            bg='#4CAF50',
            fg='white',
            pady=10
        )
        title.pack(fill=tk.X)
        
        # 输入框架
        input_frame = tk.Frame(self.root)
        input_frame.pack(fill=tk.X, padx=10, pady=10)
        
        self.entry = tk.Entry(
            input_frame,
            font=('Arial', 12),
            bd=2,
            relief=tk.GROOVE
        )
        self.entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        
        add_btn = tk.Button(
            input_frame,
            text="添加",
            command=self.add_todo,
            bg='#4CAF50',
            fg='white',
            font=('Arial', 10, 'bold'),
            padx=20
        )
        add_btn.pack(side=tk.RIGHT)
        
        # 绑定回车键
        self.entry.bind('<Return>', lambda e: self.add_todo())
        
        # 列表框架
        list_frame = tk.Frame(self.root)
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        
        # 滚动条
        scrollbar = tk.Scrollbar(list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 列表框
        self.listbox = tk.Listbox(
            list_frame,
            font=('Arial', 11),
            bd=0,
            highlightthickness=0,
            selectbackground='#a6a6a6',
            activestyle="none",
            yscrollcommand=scrollbar.set
        )
        self.listbox.pack(fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.listbox.yview)
        
        # 按钮框架
        button_frame = tk.Frame(self.root)
        button_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        complete_btn = tk.Button(
            button_frame,
            text="完成",
            command=self.complete_todo,
            bg='#2196F3',
            fg='white',
            font=('Arial', 10, 'bold')
        )
        complete_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        delete_btn = tk.Button(
            button_frame,
            text="删除",
            command=self.delete_todo,
            bg='#f44336',
            fg='white',
            font=('Arial', 10, 'bold')
        )
        delete_btn.pack(side=tk.LEFT, padx=5)
        
        clear_btn = tk.Button(
            button_frame,
            text="清空",
            command=self.clear_all,
            bg='#FF9800',
            fg='white',
            font=('Arial', 10, 'bold')
        )
        clear_btn.pack(side=tk.RIGHT)
    
    def add_todo(self):
        todo = self.entry.get().strip()
        if todo:
            self.listbox.insert(tk.END, f"□ {todo}")
            self.todos.append(todo)
            self.entry.delete(0, tk.END)
    
    def complete_todo(self):
        selection = self.listbox.curselection()
        if selection:
            index = selection[0]
            current_text = self.listbox.get(index)
            if current_text.startswith("□"):
                new_text = current_text.replace("□", "☑", 1)
            else:
                new_text = current_text.replace("☑", "□", 1)
            self.listbox.delete(index)
            self.listbox.insert(index, new_text)
    
    def delete_todo(self):
        selection = self.listbox.curselection()
        if selection:
            index = selection[0]
            self.listbox.delete(index)
            del self.todos[index]
    
    def clear_all(self):
        self.listbox.delete(0, tk.END)
        self.todos.clear()
    
    def run(self):
        self.root.mainloop()

# 运行待办事项应用
if __name__ == "__main__":
    app = TodoApp()
    app.run()

最佳实践

代码组织

# 使用类组织代码
class Application:
    def __init__(self):
        self.root = tk.Tk()
        self.setup_ui()
    
    def setup_ui(self):
        # 设置UI组件
        pass
    
    def run(self):
        self.root.mainloop()

# 分离UI和逻辑
class UI:
    def __init__(self, root):
        self.root = root
        self.create_widgets()
    
    def create_widgets(self):
        pass

class Logic:
    def __init__(self, ui):
        self.ui = ui
    
    def process_data(self):
        pass

错误处理

def safe_operation():
    try:
        # 可能出错的操作
        result = risky_function()
        return result
    except ValueError as e:
        messagebox.showerror("错误", f"数值错误: {e}")
    except FileNotFoundError as e:
        messagebox.showerror("错误", f"文件未找到: {e}")
    except Exception as e:
        messagebox.showerror("错误", f"未知错误: {e}")
    finally:
        # 清理操作
        pass

性能优化

# 使用after代替循环
def update_clock():
    current_time = time.strftime("%H:%M:%S")
    clock_label.config(text=current_time)
    root.after(1000, update_clock)  # 1秒后再次调用

# 批量更新UI
def update_multiple_widgets():
    widgets = [widget1, widget2, widget3]
    for widget in widgets:
        widget.config(state='disabled')
    
    # 执行耗时操作
    
    for widget in widgets:
        widget.config(state='normal')