平时写代码、查资料最频繁的动作就是复制粘贴。可系统自带的剪贴板只能保存最近一次复制内容,一旦连续复制多段文字,前面的记录就悄悄被覆盖了。尤其当我在整理报错信息、对比多段配置,或者在几个文档之间来回搬内容时,这种“单条剪贴板”带来的返工成本非常高。
我想过直接下载第三方剪贴板管理工具,但说实话,需求并不复杂,而且自己写一个反而更可控:数据存在本地、隐私自己掌控、功能随时能改。于是我用 Python 花了不到半天写了一款带历史记录、搜索和恢复功能的剪贴板小工具。这篇文章把完整实现过程拆开讲一遍,包含环境准备、核心代码、GUI 界面、运行演示和常见问题排查。
如果你是 Python 初学者,可以作为综合练习项目来学习;如果你有开发经验,也可以拿这版代码作为基线,扩展 OCR、快捷键唤起、多端同步等能力。
1. “剪贴板管理器”到底解决了什么问题
1.1 系统剪贴板的先天限制
在 Windows、macOS 或大多数 Linux 桌面环境中,系统剪贴板的设计极其简单:全局只有一个共享区域,任何时候你执行 Ctrl + C,新的内容会直接覆盖旧内容。换句话说,剪贴板只保留“最后一次复制”的结果。
这在日常输入场景下够用,但在下面这些情况下会非常痛苦:
- 连续复制多段日志、多个报错文本,需要逐一比对;
- 从网页复制一段内容后,又复制了另一段,第一段就找不回来了;
- 在多个文档之间搬迁资料,需要反复切换窗口、重复复制;
- 复制了重要信息,结果手滑又复制了一行无意义内容,导致之前的信息彻底丢失。
如果系统自带剪贴板能保存最近 50 条历史,这些问题就不存在了。可惜主流系统默认都没有提供这个功能。
1.2 剪贴板管理器是什么
剪贴板管理器是在系统剪贴板之上增加一层“历史记录”能力的软件。它一直在后台监听剪贴板的变化,每当检测到新的内容被复制,就自动存入本地历史库。用户随时可以打开历史列表,搜索过去复制过的内容,点击某一条即可恢复复制到系统剪贴板。
它的核心技术原理并不复杂:
- 读取系统剪贴板内容;
- 每隔一小段时间轮询一次,和上一次读取到的内容比较;
- 如果发生变化,说明用户执行了新的复制操作,就把新内容保存到本地;
- 提供搜索、查看和恢复功能,让用户能重新取回历史记录。
这里的“监听”采用的是轮询方案,而不是依赖系统事件通知。原因在于各平台对剪贴板事件通知的支持并不统一,跨平台实现成本较高;而轮询方案实现简单,兼容性好,每 0.5 秒读取一次文本内容的损耗也几乎可以忽略。
1.3 自己写一个剪贴板工具意味着什么
自己写剪贴板管理器,并不是重复造轮子,而是有实际价值的技术练习:
- 可以完全掌握数据存储位置,不打个人敏感数据放到第三方软件的服务端;
- 可以按自己的使用习惯定制功能,而不是被动适配已有软件的交互逻辑;
- 可以顺带练习 Python 中文件读写、跨平台库调用、多线程编程、GUI 界面构建等综合能力。
这篇文章实现的工具,功能可以概括为五条:
- 自动监听剪贴板变化,保存历史记录;
- 相同内容自动去重,保留最新复制的那条;
- 支持按关键词搜索历史内容;
- 双击列表项,可将内容重新复制回系统剪贴板;
- 历史记录以 JSON 格式持久化到本地,程序重启不丢失。
整体代码量在 200 行左右,结构清晰,适合一步一步复现。
2. 环境准备与版本说明
在开始写代码前,先确认本地环境能满足运行要求。
2.1 运行环境
本文示例以 Python 3.8 为基准,理论上 Python 3.6 以上版本都可以运行。如果你本机安装的是更高版本,例如 Python 3.10、3.11,也完全可以。
需要准备的环境如下:
- 操作系统:Windows 10/11、macOS、Linux(带桌面环境);
- Python:3.8 及以上版本;
- 第三方库:pyperclip;
- GUI 库:tkinter,它是 Python 标准库的一部分,不需要额外安装;
- IDE:推荐使用 PyCharm、VS Code,普通文本编辑器也可以。
先检查 Python 环境是否正常:
python --version pip --version如果python命令无法识别,需要检查 Python 是否已加入系统 PATH 环境变量。
2.2 安装 pyperclip
pyperclip是操作剪贴板的跨平台 Python 库,安装非常简单:
pip install pyperclip如果你所在网络环境下载速度慢,可以切换为国内镜像源:
pip install pyperclip -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,先做一个快速验证:
python -c "import pyperclip; pyperclip.copy('hello clipboard'); print(pyperclip.paste())"如果终端输出hello clipboard,说明 pyperclip 能正常读写系统剪贴板。
2.3 Linux 环境的注意事项
在 Linux 桌面环境下,pyperclip 默认依赖xclip或xsel这类外部命令才能访问剪贴板。如果运行时报类似PyperclipException: Could not copy的错误,需要先安装依赖:
Debian / Ubuntu 系:
sudo apt install xclipRHEL / CentOS 系:
sudo yum install xsel安装完成后重新运行上面的验证命令即可。
另外需要提醒的是,如果你使用的是 Wayland 显示服务器,部分环境下 pyperclip 可能无法直接访问剪贴板,需要安装wl-clipboard并进行相应配置。这个属于特殊情况,遇到时再单独处理。
2.4 项目结构规划
为了保持代码清晰,本项目只有一个主文件,运行后会自动生成一个 JSON 数据文件。项目结构如下:
clipboard-tool/ ├── clipboard_app.py └── clipboard_history.json # 运行后自动生成clipboard_app.py是唯一的源码文件,里面包含两个类:
ClipboardManager:负责剪贴板历史记录的存储、读取、搜索、恢复以及监听线程;ClipboardApp:负责基于 tkinter 的 GUI 界面,绑定用户操作事件。
这样拆分的目的是把“数据逻辑”和“界面逻辑”分开,后续如果要改成命令行版本,或者替换成其他 GUI 框架,可以复用ClipboardManager。
3. 数据存储设计与核心逻辑
3.1 为什么选择 JSON 存储
剪贴板历史记录本质上是“文本集合”,每条记录包括内容、时间和唯一标识。最简单的存储方案是纯文本文件,但每次新增、删除、去重都需要自行处理,实现起来容易出错。
使用 JSON 有三个明显好处:
- 数据结构天然支持嵌套,适合表示列表型历史记录;
- Python 标准库
json序列化/反序列化非常成熟,无需引入数据库; - 数据文件是纯文本,可以用任意编辑器查看和修改,方便调试。
数据文件clipboard_history.json的最终形态如下:
{ "history": [ { "id": "a3f6c1e2-9d4e-4b7a-8f0d-2c9e01f4d8a1", "content": "这是一段剪贴板历史内容", "time": "2025-01-15 14:30:22" }, { "id": "b8e7d2c4-1a5f-4c6b-93a1-6d7c0b2f9e5a", "content": "SELECT * FROM users WHERE status = 1", "time": "2025-01-15 14:31:08" } ] }这里使用id字段作为每条记录的唯一标识,目的是方便删除和恢复操作,避免用内容或时间作为定位依据出现歧义。
3.2 类设计:ClipboardManager
现在开始实现核心数据管理类。完整代码如下,我会分块解释每一部分的作用。
import json import os import time import uuid import pyperclip from datetime import datetime from threading import Event, Thread class ClipboardManager: """负责剪贴板历史记录的存储、检索与恢复""" def __init__(self, storage_path="clipboard_history.json", max_items=50): self.storage_path = storage_path self.max_items = max_items self.history = self._load_history() self._stop_event = Event()3.2.1 历史记录加载逻辑
_load_history方法在程序启动时读取 JSON 文件。如果文件不存在,返回空列表;如果文件内容损坏,则先把损坏文件改名备份,再返回空列表,避免程序直接崩溃。
def _load_history(self): if os.path.exists(self.storage_path): try: with open(self.storage_path, "r", encoding="utf-8") as f: data = json.load(f) return data.get("history", []) except Exception: backup = self.storage_path + ".bak" try: os.rename(self.storage_path, backup) except Exception: pass return [] return []这里有意识处理了“文件损坏”的情况:直接读取一份损坏的 JSON 文件会抛出JSONDecodeError。如果因为意外断电、磁盘写入中断导致文件损坏,程序不能因此起不来,备份后重建历史是比较稳妥的处理方式。
3.2.2 历史记录保存逻辑
保存时使用ensure_ascii=False,保证中文内容以原文形式写入文件,而不是被转义成\uXXXX格式。indent=2则让文件保留缩进,方便手工查看。
def _save_history(self): try: with open(self.storage_path, "w", encoding="utf-8") as f: json.dump({"history": self.history}, f, ensure_ascii=False, indent=2) except Exception as e: print(f"[Save Error] {e}")3.2.3 新记录添加与去重逻辑
add_item是核心写入方法。需要注意几个要点:
- 先去掉首尾空白,避免复制时误带入换行符;
- 如果历史中已存在相同内容,先删除旧记录,再把新记录插入到最前面;
- 设置容量上限,防止历史文件无限膨胀。
def add_item(self, text): text = text.strip() if not text: return # 去重:相同内容只保留最新一条 self.history = [item for item in self.history if item["content"] != text] item = { "id": str(uuid.uuid4()), "content": text, "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } self.history.insert(0, item) if len(self.history) > self.max_items: self.history = self.history[:self.max_items] self._save_history()关于去重策略,这里采用的是“相同内容保留最新一条并移到最前”。这样既不会产生大量重复记录,也不会因为内容相同而丢失时间最新信息。对于日常使用来说,这种策略比“保留所有重复记录再额外标记唯一 ID”更省空间,也更符合直觉。
3.2.4 查询、恢复与删除
search方法使用简单字符串匹配实现搜索。为了提高搜索范围,把关键字和内容都转成小写后再比较,实现不区分大小写的搜索。
restore方法通过id在历史中找到记录,然后将内容重新写入系统剪贴板。
delete_item和clear分别用于单条删除和全部清空。
def get_all_items(self): return list(self.history) def search(self, keyword): if not keyword: return self.get_all_items() keyword = keyword.lower() return [item for item in self.history if keyword in item["content"].lower()] def restore(self, item_id): for item in self.history: if item["id"] == item_id: pyperclip.copy(item["content"]) return item["content"] return None def delete_item(self, item_id): self.history = [item for item in self.history if item["id"] != item_id] self._save_history() def clear(self): self.history = [] self._save_history()注意get_all_items返回的是list(self.history)新列表,而不是直接返回内部引用。这样外部代码修改返回列表时,不会意外改动到ClipboardManager内部的数据,降低了耦合风险。
3.3 剪贴板监听线程
监听是剪贴板管理器最核心的机制。思路很简单:启动一个后台循环,每隔一段固定时间读取剪贴板当前文本,与上一次读取到的文本做比较,如果出现内容变化,就调用add_item写入历史。
def monitor(self, interval=0.5): last_text = pyperclip.paste() while not self._stop_event.is_set(): try: current_text = pyperclip.paste() if current_text != last_text: if current_text.strip(): self.add_item(current_text) last_text = current_text except Exception as e: print(f"[Monitor Error] {e}") time.sleep(interval) def start_monitor(self): self._stop_event.clear() thread = Thread(target=self.monitor, daemon=True) thread.start() return thread def stop_monitor(self): self._stop_event.set()这里有几个设计细节值得解释:
- 使用
daemon=True创建后台线程。这样当 GUI 主窗口关闭时,线程不会阻塞 Python 进程退出; - 使用
_stop_event实现线程停止。Event是线程安全的事件标志,调用stop_monitor后,循环会在下一次迭代时退出,不会粗暴地强制中断线程; - 轮询间隔默认 0.5 秒。如果设得太短,比如 0.01 秒,CPU 占用会明显上升;设得太长,比如 3 秒,用户复制完内容后要等较长时间才出现在历史里。0.5 秒是比较均衡的取值。
还有一个容易忽略的细节:当用户在 GUI 中点击“复制选中内容”恢复旧记录时,pyperclip.copy()会把内容写入系统剪贴板,监听线程检测到变化后,会再次触发add_item。但因为在add_item中已经实现了去重,这段被恢复的内容会重新提升到历史记录的顶部,不会产生重复记录。这种“副作用”反而符合“最近使用的内容排在最上方”的预期。
4. GUI 界面设计与完整代码
数据层已经就绪,接下来实现基于 tkinter 的可视化界面。
界面布局包含三个区域:
- 顶部:搜索框和清空按钮;
- 中部:历史记录列表;
- 底部:操作按钮和状态栏。
完整代码如下,运行前请先确认已安装 pyperclip:
import tkinter as tk from tkinter import ttk, messagebox import json import os import time import uuid import pyperclip from datetime import datetime from threading import Event, Thread class ClipboardManager: """负责剪贴板历史记录的存储、检索与恢复""" def __init__(self, storage_path="clipboard_history.json", max_items=50): self.storage_path = storage_path self.max_items = max_items self.history = self._load_history() self._stop_event = Event() def _load_history(self): if os.path.exists(self.storage_path): try: with open(self.storage_path, "r", encoding="utf-8") as f: data = json.load(f) return data.get("history", []) except Exception: backup = self.storage_path + ".bak" try: os.rename(self.storage_path, backup) except Exception: pass return [] return [] def _save_history(self): try: with open(self.storage_path, "w", encoding="utf-8") as f: json.dump({"history": self.history}, f, ensure_ascii=False, indent=2) except Exception as e: print(f"[Save Error] {e}") def add_item(self, text): text = text.strip() if not text: return self.history = [item for item in self.history if item["content"] != text] item = { "id": str(uuid.uuid4()), "content": text, "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } self.history.insert(0, item) if len(self.history) > self.max_items: self.history = self.history[:self.max_items] self._save_history() def get_all_items(self): return list(self.history) def search(self, keyword): if not keyword: return self.get_all_items() keyword = keyword.lower() return [item for item in self.history if keyword in item["content"].lower()] def restore(self, item_id): for item in self.history: if item["id"] == item_id: pyperclip.copy(item["content"]) return item["content"] return None def delete_item(self, item_id): self.history = [item for item in self.history if item["id"] != item_id] self._save_history() def clear(self): self.history = [] self._save_history() def monitor(self, interval=0.5): last_text = pyperclip.paste() while not self._stop_event.is_set(): try: current_text = pyperclip.paste() if current_text != last_text: if current_text.strip(): self.add_item(current_text) last_text = current_text except Exception as e: print(f"[Monitor Error] {e}") time.sleep(interval) def start_monitor(self): self._stop_event.clear() thread = Thread(target=self.monitor, daemon=True) thread.start() return thread def stop_monitor(self): self._stop_event.set() class ClipboardApp: """基于 tkinter 的可视化操作界面""" def __init__(self, root): self.manager = ClipboardManager() self.current_items = [] self.root = root self.root.title("随手剪贴板") self.root.geometry("680x480") self.root.minsize(520, 360) self._build_ui() self.manager.start_monitor() self.refresh_list() self.root.protocol("WM_DELETE_WINDOW", self.on_close) def _build_ui(self): top_frame = ttk.Frame(self.root, padding=10) top_frame.pack(fill=tk.X) ttk.Label(top_frame, text="关键词").pack(side=tk.LEFT) self.search_var = tk.StringVar() search_entry = ttk.Entry(top_frame, textvariable=self.search_var) search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=8) search_entry.bind("<KeyRelease>", self.on_search) ttk.Button(top_frame, text="清空", command=self.clear_history).pack(side=tk.RIGHT) list_frame = ttk.Frame(self.root) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) self.tree = ttk.Treeview(list_frame, columns=("time", "content"), show="headings") self.tree.heading("time", text="复制时间") self.tree.heading("content", text="内容") self.tree.column("time", width=160, anchor="center") self.tree.column("content", width=460) scrollbar = ttk.Scrollbar(list_frame, orient="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("<Double-1>", lambda e: self.copy_selected()) bottom_frame = ttk.Frame(self.root, padding=10) bottom_frame.pack(fill=tk.X) ttk.Button(bottom_frame, text="复制选中内容", command=self.copy_selected).pack(side=tk.LEFT) ttk.Button(bottom_frame, text="删除选中", command=self.delete_selected).pack(side=tk.LEFT, padx=5) self.status_bar = ttk.Label(bottom_frame, text="就绪", anchor="e") self.status_bar.pack(side=tk.RIGHT) def on_search(self, event=None): keyword = self.search_var.get() self.refresh_list(self.manager.search(keyword)) def refresh_list(self, items=None): if items is None: items = self.manager.get_all_items() self.current_items = items self.tree.delete(*self.tree.get_children()) for idx, item in enumerate(items): self.tree.insert("", tk.END, iid=str(idx), values=(item["time"], item["content"])) def copy_selected(self): selected = self.tree.selection() if not selected: messagebox.showinfo("提示", "请先选择一条记录") return idx = int(selected[0]) item = self.current_items[idx] self.manager.restore(item["id"]) suffix = "..." if len(item["content"]) > 30 else "" self.status_bar.config(text=f"已复制:{item['content'][:30]}{suffix}") def delete_selected(self): selected = self.tree.selection() if not selected: return idx = int(selected[0]) item = self.current_items[idx] self.manager.delete_item(item["id"]) self.refresh_list() self.status_bar.config(text="已删除一条记录") def clear_history(self): if messagebox.askyesno("确认", "确定要清空所有剪贴板历史记录吗?"): self.manager.clear() self.refresh_list() self.status_bar.config(text="历史记录已清空") def on_close(self): self.manager.stop_monitor() self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = ClipboardApp(root) root.mainloop()5. 运行与效果演示
5.1 启动程序
在项目目录下打开终端执行:
python clipboard_app.py启动后,屏幕会弹出窗口,标题是“随手剪贴板”。窗口左侧是历史列表,顶部有输入框,底部有“复制选中内容”和“删除选中”按钮。
5.2 模拟实际使用场景
为了验证效果,可以按照下面的步骤操作:
- 保持程序运行,切换到任意编辑器;
- 连续复制几段不同内容,例如:
- 复制
Python 剪贴板管理器实现 - 复制
import pyperclip - 复制
tkinter 是 Python 标准 GUI 库
- 复制
- 切回程序窗口,会看到三条记录按时间倒序排列,最新复制的内容排在最上方;
- 在顶部搜索框输入
剪贴板,列表会过滤出包含该关键词的记录; - 双击某条记录,对应内容会被重新写回系统剪贴板;
- 回到编辑器按 Ctrl + V,能粘贴刚刚恢复的内容。
5.3 数据持久化验证
关闭程序后再次启动,历史记录仍然存在。可以打开项目目录下的clipboard_history.json文件查看:
{ "history": [ { "id": "f7d4a1b2-6c8e-4f3a-912b-0d2c8f9e6a34", "content": "tkinter 是 Python 标准 GUI 库", "time": "2025-01-15 15:10:33" } ] }只要这个文件没有被手动删除或破坏,历史数据就会一直保留。
6. 常见问题与排查思路
在实际运行过程中,新手容易遇到一些固定问题,下面用表格汇总排查思路。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 启动报错 PyperclipException | Linux 环境缺少 xclip/xsel | 安装对应依赖后重启程序 |
| 界面能打开,但复制内容不记录 | 监听线程未启动或剪贴板权限异常 | 检查终端是否打印 Monitor Error,确认 pyperclip 可正常读写 |
| 中文内容在 JSON 文件中显示为 \uXXXX | 保存时未使用 ensure_ascii=False | 检查代码中 json.dump 的参数 |
| 恢复旧记录后,该记录回到列表顶部 | 监听捕获到自己写入的剪贴板内容 | 这是去重策略的正常行为,无需处理 |
| 历史文件损坏导致启动失败 | 文件写入中断或手工编辑出错 | 程序会自动改名为 .bak 并重建历史,启动后手动检查备份即可 |
| CPU 占用率明显偏高 | 轮询间隔设置过短 | 将 monitor 的 interval 调整为 0.5 秒以上 |
6.1 为什么复制内容没有出现在列表里
这个问题最常见,原因可以分四步排查:
- 确认 pyperclip 能正常读写剪贴板。可以单独运行验证命令,排除依赖问题;
- 确认程序启动时没有抛异常。如果
start_monitor在线程启动前就报错,监听自然不会运行; - 确认监视线程是否进入死循环。检查
monitor方法中是否有try-except挡住了异常; - 确认复制的内容是否为空文本。程序默认忽略空白内容,这是有意为之。
6.2 恢复后内容被重新入列,是否算 Bug
这个问题经常被问到。当用户在列表中双击某条旧记录,程序执行pyperclip.copy()后,系统剪贴板的值变成了这条旧内容。监听线程检测到变化,会触发add_item。因为我们设置了去重逻辑,所以历史中不会新增重复记录,只会把该记录的时间更新并提升到顶部。
从用户视角看,这个行为是合理的:最近恢复使用的内容,理应排在最前面。因此这不属于 Bug,而是功能选择。
7. 工程落地与后续扩展建议
基础版本已经能解决日常需求。但既然是工程实践,我建议你继续从以下方向优化和维护这个项目。
7.1 持久化行为优化
当前版本在每次新增、删除、清空时都会立即写文件。在复制频率极高的场景下,这种同步写入方式可能带来不必要的 I/O 损耗。
更优的做法是采用“定时批量写入 + 程序退出时强刷”的组合方案:
- 内存中维护历史数据;
- 监听线程每次变化只更新内存;
- 每 10 秒或 30 秒自动落盘一次;
- 窗口关闭时强制保存一次。
这样可以降磁盘写入频率,同时避免程序异常退出时丢失大量数据。
7.2 隐私与安全
剪贴板中经常会出现密码、验证码、密钥、临时文件路径等敏感信息。把这个工具长期挂在后台,相当于默认记录了所有复制过的内容。因此需要重点考虑:
- 不要在没有加密的存储文件中保存内容;
- 可以在 GUI 中增加“排除敏感内容”的过滤规则;
- 可以为历史文件增加密码锁或直接使用系统钥匙串方案;
- 在公共电脑上使用后,记得清空历史文件。
7.3 功能扩展方向
这个项目可以按需求做得更深入:
- 支持图片剪贴板历史:当前实现只处理文本,如果想保存截图,需要把剪贴板里的位图数据转成文件保存;
- 支持 OCR 识别:结合 PaddleOCR 或 Tesseract,把图片中的文字提取到历史记录里;
- 支持全局快捷键唤起:在 Windows 上可以使用
keyboard库,macOS 上可以使用全局热键方案,实现一键呼出搜索框; - 支持多端同步:把历史记录保存到指定目录、Git 仓库,或接入 WebDAV、对象存储,实现多台电脑之间同步;
- 打包成 exe:使用 PyInstaller 把项目打包为独立可执行文件,脱离 Python 环境运行。
打包命令参考:
pip install pyinstaller pyinstaller -F -w clipboard_app.py其中-F表示单文件模式,-w表示不显示控制台窗口。打包完成后,在dist目录下可以得到clipboard_app.exe,可以直接拷贝到其他 Windows 机器运行。
7.4 单元测试意识
这个小工具的逻辑并不复杂,但涉及文件读写、去重、搜索和线程,非常适合用来练习单元测试。可以为ClipboardManager编写几个基础测试用例:
- 添加内容后列表数增加;
- 相同内容再次添加不会产生重复记录;
- 搜索能正确过滤内容;
- 删除指定 id 后列表中不再包含该记录;
- 容量达到上限时自动丢弃最旧记录。
这些测试能保证以后扩展功能时不至于破坏已有能力。
8. 这次动手实践的收获
回到标题里的“随手做”。这个剪贴板工具本身代码量不大,技术也都是常见内容,但要把“后台监听、数据持久化、GUI 交互、线程安全”串起来,对初学者来说是一次非常完整的小型项目练习。
从这段实现里,可以复习到的知识点包括:
- JSON 数据的序列化与反序列化;
- 后台线程的启停方式,以及如何避免线程泄漏;
- 跨平台库的选择与依赖处理;
- GUI 事件绑定和列表刷新机制;
- 去重、容量控制、异常兜底等工程细节。
如果继续往下做,可以在项目里补充打包发布、自动化测试、全局快捷键唤起、图片识别等能力。到那时,这个“随手做的小工具”就已经发展成一个完整的桌面效率工具了。
代码可以直接复制运行,遇到问题欢迎在评论区留言。如果你有更好的剪贴板管理思路,也欢迎分享你的设计方案。