前言

如果你是从上一篇《大一新生 × AI Coding:从本地到云端,我如何用DeepSeek做出一个天气查询助手》过来的,那么恭喜你,你已经学会了如何用代码和互联网对话。

但今天我们玩点不一样的。之前的三个项目——猜数字、记账本、天气查询——都是在黑乎乎的终端(命令行)里跑的。虽然功能没问题,但总感觉不够“正规”,毕竟大家平时用的软件,都是有窗口、有按钮、能拿鼠标点的。

所以这第四个项目,我们的目标很明确:把上一篇的命令行记账本,改造成一个有图形界面的桌面小工具。

这篇文章会记录我如何借助 DeepSeek,利用 Python 自带的 tkinter 库,从零写出一个能看、能点、能记账的窗口程序。最后我还会把它打包成一个 exe 文件,发给没装 Python 的朋友也能直接用。


一、为什么选择 tkinter?

提到做图形界面,Python 有很多选择,比如 PyQt、wxPython 等等,但对于新手来说,tkinter 是最好的入门选择。

理由有三点:
1. 自带库,装完 Python 就有了,不用额外 pip install。
2. 做小工具完全够用,按钮、输入框、标签这些基础组件都有。
3. DeepSeek 对 tkinter 的代码生成非常熟练,容错率高,对新手友好。

我们这次的目标,就是做出一个真正能独立运行的窗口程序。虽然界面朴素,但它是你迈向“桌面软件开发”的第一步。


二、实现思路:把“命令行逻辑”翻译成“图形界面逻辑”

其实核心的数据逻辑——存钱、取钱、保存到 JSON 文件——和上一篇的命令行记账本是完全一样的。

我们只是把“输入”和“输出”的方式换了一下:
- 原来:用 input 函数在终端里输入金额 → 现在:弹出一个新窗口让用户填金额和说明。
- 原来:用 print 打印余额 → 现在:把余额数字显示在窗口顶部的标签上。
- 原来:用 while True 循环等待输入 → 现在:窗口一直开着,等用户点击按钮,这叫事件驱动。

这种“后端逻辑不动,只换前端皮肤”的做法,在真实开发里也非常常见。


三、第一步:让 DeepSeek 生成第一版空白窗口

打开 DeepSeek,输入以下指令:

“我想用 Python 的 tkinter 写一个记账本程序。要求有一个主窗口,窗口标题叫‘我的记账本’,窗口大小 500x400。不需要任何功能,先帮我把空窗口搭起来。”

DeepSeek 会返回类似下面这样的代码。我们把它保存为 tk_account_book.py,然后运行一下看看效果。

import tkinter as tk

# 创建主窗口
root = tk.Tk()
root.title("我的记账本")
root.geometry("500x400")

# 让窗口跑起来
root.mainloop()
(代码块结束)

运行成功!一个空白的、标题正确的灰色窗口出现了。虽然里面什么都没有,但这一步标志着我们已经从黑框框迈入了图形界面的世界。


四、第二步:搭建界面布局

接下来我们要把界面丰富起来。继续问 DeepSeek:

“请在上面的空窗口中添加:顶部显示‘当前余额:0 元’;中间是一个显示多行文字的列表框(Listbox);底部放三个按钮:‘记收入’、‘记支出’、‘刷新’。不需要实现功能,先把布局排好。”

DeepSeek 会生成新的代码。复制替换进去,再次运行,你会看到界面已经有了雏形。

这时候如果觉得按钮太小或者字体不够明显,可以直接跟 DeepSeek 说:“把余额字体调成 16 号加粗”、“按钮调大一点,间距拉开一些”。AI 会立刻帮你调整好。这就是 AI Coding 最爽的地方——你只管提需求,代码细节它来写。


五、第三步:实现“记一笔账”的弹窗功能

这是最关键的一步。当用户点击“记收入”时,应该弹出一个新窗口,让用户输入金额和说明。

我给 DeepSeek 的 prompt 是这样的:

“完善 tkinter 记账本功能:点击‘记收入’按钮,弹出一个顶级窗口(Toplevel),里面有‘金额’输入框、‘说明’输入框,以及一个‘保存’按钮。点击保存后,将数据以字典形式打印到控制台,并关闭弹窗。”

DeepSeek 很快给出了代码。这一步让我深刻理解了“函数绑定事件”的概念——按钮的 command 参数,指向的就是我们定义好的函数。


六、第四步:连接“大脑”——移植记账本的核心逻辑

这一步考验的是“搬运”和“整合”能力。我们把上一篇 account_book.py 里关于 load_data、save_data、add_income 等函数,几乎原封不动地复制到新的 tk_account_book.py 中。

只需要做一点小修改:
- 把原来用 print 报错的提示,改成用 tkinter 的 messagebox.showerror,这样会弹出一个错误提示框,更符合图形界面的交互习惯。
- 保存数据成功后,除了写入 JSON 文件,还要刷新主窗口的余额显示和记录列表。

如果你觉得这部分代码有点绕,没关系,直接把两个文件的代码一起丢给 DeepSeek:

“这是我的命令行记账本代码 [贴代码],这是我现在的 tkinter 界面代码 [贴代码],请帮我把记账逻辑整合进 tkinter 界面里。”

DeepSeek 会帮你完成这场“换脑手术”。


七、完整代码(复制即用)

以下是整合完成的完整代码。你可以新建一个 tk_account_book.py 文件,把下面的内容全部复制进去,保存后直接运行。

(代码块开始,粘贴后请选中本段点击编辑器「代码块」按钮,选择 Python 语言)
import tkinter as tk
from tkinter import messagebox, simpledialog
import json
import os
from datetime import datetime

DATA_FILE = "account_book.json"

class AccountBookApp:
    def __init__(self, root):
        self.root = root
        self.root.title("我的记账本")
        self.root.geometry("500x450")
        
        self.records = []
        self.load_data()
        
        # --- 顶部:余额显示 ---
        self.balance_label = tk.Label(root, text="当前余额:0.00 元", font=("微软雅黑", 16, "bold"))
        self.balance_label.pack(pady=15)
        
        # --- 中部:记录显示框 ---
        self.list_frame = tk.Frame(root)
        self.list_frame.pack(pady=10, padx=20, fill=tk.BOTH, expand=True)
        
        scrollbar = tk.Scrollbar(self.list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.record_listbox = tk.Listbox(self.list_frame, yscrollcommand=scrollbar.set, font=("微软雅黑", 10))
        self.record_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.record_listbox.yview)
        
        # --- 底部:按钮区域 ---
        self.btn_frame = tk.Frame(root)
        self.btn_frame.pack(pady=20)
        
        self.income_btn = tk.Button(self.btn_frame, text="记收入", width=10, command=self.open_income_dialog)
        self.income_btn.pack(side=tk.LEFT, padx=10)
        
        self.expense_btn = tk.Button(self.btn_frame, text="记支出", width=10, command=self.open_expense_dialog)
        self.expense_btn.pack(side=tk.LEFT, padx=10)
        
        self.refresh_btn = tk.Button(self.btn_frame, text="刷新", width=10, command=self.refresh_display)
        self.refresh_btn.pack(side=tk.LEFT, padx=10)
        
        self.refresh_display()
    
    def load_data(self):
        if os.path.exists(DATA_FILE):
            with open(DATA_FILE, 'r', encoding='utf-8') as f:
                self.records = json.load(f)
        else:
            self.records = []
    
    def save_data(self):
        with open(DATA_FILE, 'w', encoding='utf-8') as f:
            json.dump(self.records, f, ensure_ascii=False, indent=2)
    
    def refresh_display(self):
        # 计算余额
        total = 0
        for r in self.records:
            if r["type"] == "收入":
                total += r["amount"]
            else:
                total -= r["amount"]
        self.balance_label.config(text=f"当前余额:{total:.2f} 元")
        
        # 刷新列表框
        self.record_listbox.delete(0, tk.END)
        for r in reversed(self.records):
            display_text = f"{r['time']}  {r['type']}: {r['amount']:.2f} 元  ({r['desc']})"
            self.record_listbox.insert(tk.END, display_text)
    
    def add_record(self, record_type):
        amount_str = simpledialog.askstring("输入金额", f"请输入{record_type}金额:", parent=self.root)
        if not amount_str:
            return
        try:
            amount = float(amount_str)
            if amount <= 0:
                messagebox.showerror("错误", "金额必须大于 0")
                return
        except ValueError:
            messagebox.showerror("错误", "请输入有效的数字")
            return
            
        desc = simpledialog.askstring("输入说明", "请输入说明(如:生活费、奶茶):", parent=self.root)
        if not desc:
            return
            
        record = {
            "type": record_type,
            "amount": amount,
            "desc": desc,
            "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }
        self.records.append(record)
        self.save_data()
        self.refresh_display()
        messagebox.showinfo("成功", f"已记录{record_type} {amount} 元")
    
    def open_income_dialog(self):
        self.add_record("收入")
    
    def open_expense_dialog(self):
        self.add_record("支出")

if __name__ == "__main__":
    root = tk.Tk()
    app = AccountBookApp(root)
    root.mainloop()
(代码块结束)


八、运行演示

在桌面空白处按住 Shift 键,点击鼠标右键,选择“在此处打开 PowerShell 窗口”,输入:

python tk_account_book.py

程序启动后,你会看到一个标题为“我的记账本”的窗口。

将白框放大

试着操作一下:
1. 点击“记收入”按钮 → 输入金额 100 → 输入说明“生活费” → 确定。
2. 窗口顶部的余额立刻变成 100.00 元,中间的列表框里出现了刚才的记录。
3. 点击“记支出”按钮 → 输入 25.5 → 输入说明“买水果” → 余额变成了 74.50 元。

最神奇的是,这个程序和上一篇的命令行记账本读取的是同一个 account_book.json 文件。你可以用命令行记一笔,再用图形界面打开,数据完全同步。


九、进阶玩法:把 Python 程序打包成 exe 文件

这才是最酷的一步。如果你想把这个程序发给室友用,但他电脑上没装 Python 怎么办?

我们可以用一个叫 PyInstaller 的工具,把 .py 文件打包成 Windows 可以直接双击运行的 .exe 文件。

操作步骤:
1. 打开命令提示符,安装 PyInstaller:
   pip install pyinstaller

2. 进入 tk_account_book.py 所在的文件夹,执行打包命令:
   pyinstaller -F -w tk_account_book.py

3. 等待打包完成,在生成的 dist 文件夹里,你会看到一个 tk_account_book.exe 文件。

在终端里直接打开 dist 文件夹

在 PowerShell 中输入:

bash

start dist

这会自动用资源管理器打开 dist 文件夹。

双击它,一个真正的 Windows 桌面软件就跑起来了,不需要黑框框,不需要 Python 环境。发给任何人,他们都能直接运行。


十、我学到了什么

做完这个项目,我的编程技能树终于完整了:

1. 变量和循环 → 猜数字游戏
2. 文件读写和数据持久化 → 命令行记账本
3. 网络请求和 API 调用 → 天气查询助手
4. 图形界面和事件驱动 → tkinter 记账本

我从一个对着黑框框发怵的小白,变成了能写出带窗口、能双击运行的桌面软件的小开发者。

AI Coding 帮我跳过了繁琐的语法细节,让我直接看到成果,保持学习的热情。这种感觉真的很棒。


十一、踩坑记录:中文显示乱码怎么办?

在测试过程中,我发现一个小问题:用某些编辑器保存代码后,tkinter 窗口上的中文变成了乱码。

排查后发现,原因是文件编码格式不是 UTF-8。解决方法很简单:
用记事本打开 tk_account_book.py,点击“另存为”,在底部的“编码”下拉框里选择“UTF-8”,保存替换原文件即可。

这个小插曲也让我更深刻地理解了字符编码的重要性。


十二、下一步计划

四个小项目做完,我已经初步具备了独立开发小工具的能力。接下来我打算挑战一个更完整的项目:

用 tkinter 做一个“桌面便签”或者“番茄钟计时器”,不仅能记东西,还能设置提醒。

等做出来了,我会继续写成第五篇博客。这个系列会一直更新下去,记录我从零开始学编程的每一步。

如果你也照着这篇教程做出了自己的图形界面记账本,欢迎在评论区晒图!遇到了什么问题也可以留言,我们一起讨论。


本文代码由 DeepSeek 生成,作者已实际运行验证。

AI 声明:
本文由 DeepSeek 辅助生成代码和部分文案,所有步骤均经过本人实际操作验证。
————————————————
版权声明:本文为CSDN博主「qinrunlin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qinrunlin/article/details/(发布后替换)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐