Python GUI工具示例

本页面提供了Python GUI工具的实用示例,包括使用tkinter和PyQt5创建桌面应用程序的示例。这些示例将帮助你更好地理解GUI编程的概念,创建美观实用的桌面应用。

Tkinter示例

简单计算器


import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import operator
from typing import Callable, Dict

class Calculator:
    """简单计算器"""
    
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("简单计算器")
        self.window.geometry("300x400")
        self.window.resizable(False, False)
        
        # 设置样式
        self.style = ttk.Style()
        self.style.configure('TButton', font=('Arial', 12))
        self.style.configure('TEntry', font=('Arial', 14))
        
        # 创建显示框
        self.display_var = tk.StringVar()
        self.display = ttk.Entry(
            self.window,
            textvariable=self.display_var,
            justify='right',
            font=('Arial', 20)
        )
        self.display.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')
        
        # 定义按钮布局
        self.buttons = [
            '7', '8', '9', '/',
            '4', '5', '6', '*',
            '1', '2', '3', '-',
            '0', '.', '=', '+'
        ]
        
        # 创建按钮
        self.create_buttons()
        
        # 配置网格权重
        for i in range(5):
            self.window.grid_rowconfigure(i, weight=1)
        for i in range(4):
            self.window.grid_columnconfigure(i, weight=1)
        
        # 绑定键盘事件
        self.bind_keys()
        
        # 初始化计算器状态
        self.reset_calculator()
    
    def create_buttons(self):
        """创建按钮"""
        row = 1
        col = 0
        for button in self.buttons:
            cmd = lambda x=button: self.click(x)
            btn = ttk.Button(
                self.window,
                text=button,
                command=cmd,
                width=5
            )
            btn.grid(row=row, column=col, padx=2, pady=2, sticky='nsew')
            col += 1
            if col > 3:
                col = 0
                row += 1
        
        # 添加清除按钮
        clear_btn = ttk.Button(
            self.window,
            text='C',
            command=self.clear,
            width=5
        )
        clear_btn.grid(row=row, column=col, padx=2, pady=2, sticky='nsew')
    
    def bind_keys(self):
        """绑定键盘事件"""
        self.window.bind('', lambda event: self.calculate())
        self.window.bind('', lambda event: self.backspace())
        self.window.bind('', lambda event: self.clear())
        
        # 绑定数字键
        for i in range(10):
            self.window.bind(str(i), lambda event, num=i: self.click(str(num)))
        
        # 绑定运算符
        self.window.bind('+', lambda event: self.click('+'))
        self.window.bind('-', lambda event: self.click('-'))
        self.window.bind('*', lambda event: self.click('*'))
        self.window.bind('/', lambda event: self.click('/'))
        self.window.bind('.', lambda event: self.click('.'))
    
    def reset_calculator(self):
        """重置计算器状态"""
        self.current_number = ''
        self.first_number = None
        self.operation = None
        self.start_new_number = True
        self.display_var.set('0')
    
    def click(self, value: str):
        """处理按钮点击事件"""
        if value.isdigit() or value == '.':
            if self.start_new_number:
                self.current_number = value
                self.start_new_number = False
            else:
                if value == '.' and '.' in self.current_number:
                    return
                self.current_number += value
            self.display_var.set(self.current_number)
        
        elif value in ['+', '-', '*', '/']:
            if self.first_number is None:
                self.first_number = float(self.current_number)
            else:
                self.calculate()
            self.operation = value
            self.start_new_number = True
        
        elif value == '=':
            self.calculate()
    
    def calculate(self):
        """执行计算"""
        if self.first_number is None or self.operation is None:
            return
        
        try:
            second_number = float(self.current_number)
            operations = {
                '+': operator.add,
                '-': operator.sub,
                '*': operator.mul,
                '/': operator.truediv
            }
            
            result = operations[self.operation](self.first_number, second_number)
            
            # 格式化结果
            if result.is_integer():
                self.display_var.set(int(result))
            else:
                self.display_var.set(f"{result:.8f}".rstrip('0').rstrip('.'))
            
            self.first_number = result
            self.start_new_number = True
            self.operation = None
            
        except ZeroDivisionError:
            messagebox.showerror("错误", "除数不能为零!")
            self.clear()
        except Exception as e:
            messagebox.showerror("错误", f"计算错误: {str(e)}")
            self.clear()
    
    def clear(self):
        """清除显示"""
        self.reset_calculator()
    
    def backspace(self):
        """退格"""
        if not self.start_new_number:
            self.current_number = self.current_number[:-1]
            if not self.current_number:
                self.current_number = '0'
                self.start_new_number = True
            self.display_var.set(self.current_number)
    
    def run(self):
        """运行计算器"""
        self.window.mainloop()

if __name__ == '__main__':
    calculator = Calculator()
    calculator.run()
                    

文件管理器


import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
import os
from datetime import datetime
import shutil
from typing import Optional, List
import threading
import queue

class FileManager:
    """简单文件管理器"""
    
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("文件管理器")
        self.window.geometry("800x600")
        
        # 设置样式
        self.style = ttk.Style()
        self.style.configure('Treeview', font=('Arial', 10))
        self.style.configure('TButton', font=('Arial', 10))
        
        # 创建主框架
        self.main_frame = ttk.Frame(self.window)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 创建工具栏
        self.create_toolbar()
        
        # 创建文件树
        self.create_file_tree()
        
        # 创建状态栏
        self.create_statusbar()
        
        # 初始化变量
        self.current_path = os.path.expanduser("~")
        self.queue = queue.Queue()
        
        # 更新文件列表
        self.update_file_list()
        
        # 启动消息处理线程
        self.process_queue()
    
    def create_toolbar(self):
        """创建工具栏"""
        toolbar = ttk.Frame(self.main_frame)
        toolbar.pack(fill=tk.X, padx=5, pady=5)
        
        # 上级目录按钮
        self.up_button = ttk.Button(
            toolbar,
            text="上级目录",
            command=self.go_up
        )
        self.up_button.pack(side=tk.LEFT, padx=2)
        
        # 刷新按钮
        self.refresh_button = ttk.Button(
            toolbar,
            text="刷新",
            command=self.update_file_list
        )
        self.refresh_button.pack(side=tk.LEFT, padx=2)
        
        # 新建文件夹按钮
        self.new_folder_button = ttk.Button(
            toolbar,
            text="新建文件夹",
            command=self.create_folder
        )
        self.new_folder_button.pack(side=tk.LEFT, padx=2)
        
        # 删除按钮
        self.delete_button = ttk.Button(
            toolbar,
            text="删除",
            command=self.delete_selected
        )
        self.delete_button.pack(side=tk.LEFT, padx=2)
        
        # 路径输入框
        self.path_var = tk.StringVar()
        self.path_entry = ttk.Entry(
            toolbar,
            textvariable=self.path_var
        )
        self.path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        self.path_entry.bind('', lambda e: self.navigate_to_path())
    
    def create_file_tree(self):
        """创建文件树"""
        # 创建树形视图
        columns = ('name', 'size', 'type', 'modified')
        self.tree = ttk.Treeview(
            self.main_frame,
            columns=columns,
            show='headings'
        )
        
        # 设置列标题
        self.tree.heading('name', text='名称', command=lambda: self.sort_tree('name'))
        self.tree.heading('size', text='大小', command=lambda: self.sort_tree('size'))
        self.tree.heading('type', text='类型', command=lambda: self.sort_tree('type'))
        self.tree.heading('modified', text='修改时间', command=lambda: self.sort_tree('modified'))
        
        # 设置列宽
        self.tree.column('name', width=300)
        self.tree.column('size', width=100)
        self.tree.column('type', width=100)
        self.tree.column('modified', width=150)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        # 放置组件
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 绑定双击事件
        self.tree.bind('', self.on_double_click)
        
        # 绑定右键菜单
        self.create_context_menu()
    
    def create_statusbar(self):
        """创建状态栏"""
        self.statusbar = ttk.Label(
            self.window,
            text="",
            anchor=tk.W
        )
        self.statusbar.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=2)
    
    def create_context_menu(self):
        """创建右键菜单"""
        self.context_menu = tk.Menu(self.window, tearoff=0)
        self.context_menu.add_command(label="打开", command=self.open_selected)
        self.context_menu.add_command(label="复制", command=self.copy_selected)
        self.context_menu.add_command(label="剪切", command=self.cut_selected)
        self.context_menu.add_command(label="粘贴", command=self.paste_files)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="删除", command=self.delete_selected)
        self.context_menu.add_command(label="重命名", command=self.rename_selected)
        
        self.tree.bind('', self.show_context_menu)
    
    def show_context_menu(self, event):
        """显示右键菜单"""
        try:
            self.context_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.context_menu.grab_release()
    
    def update_file_list(self):
        """更新文件列表"""
        # 清空树形视图
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        try:
            # 获取文件列表
            items = os.listdir(self.current_path)
            
            # 添加文件和文件夹
            for item in items:
                full_path = os.path.join(self.current_path, item)
                try:
                    stats = os.stat(full_path)
                    
                    # 获取文件信息
                    size = self.format_size(stats.st_size)
                    modified = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')
                    
                    if os.path.isdir(full_path):
                        type = "文件夹"
                        size = ""
                    else:
                        type = os.path.splitext(item)[1][1:].upper() or "文件"
                    
                    self.tree.insert('', 'end', values=(item, size, type, modified))
                    
                except Exception as e:
                    print(f"Error processing {item}: {e}")
            
            # 更新路径显示
            self.path_var.set(self.current_path)
            
            # 更新状态栏
            self.update_status()
            
        except Exception as e:
            messagebox.showerror("错误", f"无法访问目录: {str(e)}")
    
    def format_size(self, size: int) -> str:
        """格式化文件大小"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size < 1024:
                return f"{size:.1f} {unit}"
            size /= 1024
        return f"{size:.1f} PB"
    
    def update_status(self):
        """更新状态栏"""
        try:
            items = os.listdir(self.current_path)
            files = sum(1 for item in items if os.path.isfile(os.path.join(self.current_path, item)))
            folders = sum(1 for item in items if os.path.isdir(os.path.join(self.current_path, item)))
            self.statusbar.config(text=f"{folders} 个文件夹,{files} 个文件")
        except Exception as e:
            self.statusbar.config(text="")
    
    def go_up(self):
        """转到上级目录"""
        parent = os.path.dirname(self.current_path)
        if parent != self.current_path:
            self.current_path = parent
            self.update_file_list()
    
    def navigate_to_path(self):
        """导航到指定路径"""
        path = self.path_var.get()
        if os.path.exists(path):
            self.current_path = path
            self.update_file_list()
        else:
            messagebox.showerror("错误", "路径不存在!")
    
    def on_double_click(self, event):
        """处理双击事件"""
        selection = self.tree.selection()
        if selection:
            item = self.tree.item(selection[0])
            name = item['values'][0]
            path = os.path.join(self.current_path, name)
            
            if os.path.isdir(path):
                self.current_path = path
                self.update_file_list()
            else:
                self.open_file(path)
    
    def open_file(self, path: str):
        """打开文件"""
        try:
            os.startfile(path)
        except Exception as e:
            messagebox.showerror("错误", f"无法打开文件: {str(e)}")
    
    def create_folder(self):
        """创建新文件夹"""
        name = filedialog.askstring("新建文件夹", "请输入文件夹名称:")
        if name:
            path = os.path.join(self.current_path, name)
            try:
                os.mkdir(path)
                self.update_file_list()
            except Exception as e:
                messagebox.showerror("错误", f"创建文件夹失败: {str(e)}")
    
    def delete_selected(self):
        """删除选中的文件或文件夹"""
        selection = self.tree.selection()
        if not selection:
            return
        
        if not messagebox.askyesno("确认", "确定要删除选中的项目吗?"):
            return
        
        for item in selection:
            values = self.tree.item(item)['values']
            path = os.path.join(self.current_path, values[0])
            try:
                if os.path.isdir(path):
                    shutil.rmtree(path)
                else:
                    os.remove(path)
            except Exception as e:
                messagebox.showerror("错误", f"删除失败: {str(e)}")
        
        self.update_file_list()
    
    def rename_selected(self):
        """重命名选中的文件或文件夹"""
        selection = self.tree.selection()
        if not selection:
            return
        
        old_name = self.tree.item(selection[0])['values'][0]
        new_name = filedialog.askstring("重命名", "请输入新名称:", initialvalue=old_name)
        
        if new_name and new_name != old_name:
            old_path = os.path.join(self.current_path, old_name)
            new_path = os.path.join(self.current_path, new_name)
            try:
                os.rename(old_path, new_path)
                self.update_file_list()
            except Exception as e:
                messagebox.showerror("错误", f"重命名失败: {str(e)}")
    
    def copy_selected(self):
        """复制选中的文件或文件夹"""
        selection = self.tree.selection()
        if selection:
            self.clipboard = []
            self.clipboard_op = 'copy'
            for item in selection:
                name = self.tree.item(item)['values'][0]
                self.clipboard.append(os.path.join(self.current_path, name))
    
    def cut_selected(self):
        """剪切选中的文件或文件夹"""
        selection = self.tree.selection()
        if selection:
            self.clipboard = []
            self.clipboard_op = 'cut'
            for item in selection:
                name = self.tree.item(item)['values'][0]
                self.clipboard.append(os.path.join(self.current_path, name))
    
    def paste_files(self):
        """粘贴文件或文件夹"""
        if not hasattr(self, 'clipboard') or not self.clipboard:
            return
        
        for src in self.clipboard:
            if not os.path.exists(src):
                continue
            
            dst = os.path.join(self.current_path, os.path.basename(src))
            try:
                if self.clipboard_op == 'copy':
                    if os.path.isdir(src):
                        shutil.copytree(src, dst)
                    else:
                        shutil.copy2(src, dst)
                elif self.clipboard_op == 'cut':
                    shutil.move(src, dst)
            except Exception as e:
                messagebox.showerror("错误", f"粘贴失败: {str(e)}")
        
        if self.clipboard_op == 'cut':
            self.clipboard = []
        
        self.update_file_list()
    
    def sort_tree(self, col):
        """排序树形视图"""
        # 获取所有项目
        items = [(self.tree.set(item, col), item) for item in self.tree.get_children('')]
        
        # 排序
        items.sort()
        
        # 重新插入项目
        for index, (val, item) in enumerate(items):
            self.tree.move(item, '', index)
    
    def process_queue(self):
        """处理消息队列"""
        try:
            while True:
                func, args = self.queue.get_nowait()
                func(*args)
                self.queue.task_done()
        except queue.Empty:
            self.window.after(100, self.process_queue)
    
    def run(self):
        """运行文件管理器"""
        self.window.mainloop()

if __name__ == '__main__':
    file_manager = FileManager()
    file_manager.run()