当前位置: 首页 > news >正文

【开源工具】Python+PyQt5打造智能桌面单词记忆工具:悬浮窗+热键切换+自定义词库

📚【深度解析】Python+PyQt5打造智能桌面单词记忆工具:悬浮窗+热键切换+自定义词库

在这里插入图片描述
请添加图片描述

🌈 个人主页:创客白泽 - CSDN博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦

请添加图片描述

在这里插入图片描述

一、概述:当单词记忆遇上Python GUI

在英语学习过程中,高频重复是记忆单词的关键。传统背单词软件往往需要用户主动打开应用,而本项目的创新之处在于开发了一个桌面悬浮窗单词记忆工具,具有以下核心特点:

  • 无干扰学习:半透明悬浮窗始终置顶显示
  • 智能记忆算法:支持顺序/逆序/随机三种循环模式
  • 快捷交互:F8热键一键切换中英释义
  • 高度可定制:字体/颜色/间隔时间全面可配置
  • 轻量化设计:系统托盘运行,内存占用<50MB

本文将深入解析200+行代码的实现原理,并提供完整的可执行方案。

二、功能架构设计

在这里插入图片描述

2.1 核心功能模块

  1. 单词显示模块

    • 定时切换显示
    • 中英双语切换
    • 视觉样式定制
  2. 交互控制模块

    • 全局热键监听
    • 拖拽移动窗口
    • 系统托盘菜单
  3. 配置管理模块

    • QSettings持久化
    • 词库动态加载
    • 设置实时生效

2.2 技术选型对比

技术方案优势本项目选择原因
PyQt5成熟GUI框架跨平台支持好
pynput全局热键监听比win32api更简洁
QSettings配置存储无需额外数据库

三、效果展示

3.1 主界面效果

在这里插入图片描述

3.2 设置面板

在这里插入图片描述

3.3 系统托盘菜单

在这里插入图片描述

四、实现步骤详解

4.1 环境准备

pip install PyQt5 pynput

4.2 词库文件格式

创建words.txt(每行一个单词+释义):

apple 苹果
banana 香蕉

4.3 核心类解析

4.3.1 热键监听线程
class HotkeyWorker(QObject):toggle_signal = pyqtSignal()def run(self):from pynput import keyboarddef on_activate_f8():self.toggle_signal.emit()with keyboard.GlobalHotKeys({'<F8>': on_activate_f8}) as listener:listener.join()

关键点

  • 使用QObject实现跨线程信号通信
  • pynput库实现全局热键监听
  • 异常处理保障稳定性
4.3.2 主窗口类
class WordDisplayApp(QMainWindow):def __init__(self):super().__init__()self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)self.setAttribute(Qt.WA_TranslucentBackground)

窗口特性

  • FramelessWindowHint:无边框
  • WindowStaysOnTopHint:始终置顶
  • WA_TranslucentBackground:半透明效果

4.4 配置持久化实现

self.settings = QSettings("WordDisplay", "WordDisplayApp")# 保存配置
self.settings.setValue("interval", self.interval)# 读取配置
self.interval = self.settings.value("interval", 5, type=int)

存储位置

  • Windows:注册表HKEY_CURRENT_USER\Software\WordDisplay\WordDisplayApp
  • Mac:~/Library/Preferences/com.WordDisplay.WordDisplayApp.plist

五、代码深度解析

5.1 单词加载算法

def load_words(self):if self.order == "reverse":self.word_list.reverse()elif self.order == "random":random.shuffle(self.word_list)

记忆算法优化

  • 随机模式:避免固定顺序记忆
  • 逆序模式:强化尾部单词记忆
  • 间隔重复:通过定时器控制显示节奏

5.2 拖拽移动实现

def mousePressEvent(self, event):self.dragging = Trueself.offset = event.globalPos() - self.pos()def mouseMoveEvent(self, event):if self.dragging:self.move(event.globalPos() - self.offset)

交互细节

  • 记录鼠标点击位置与窗口位置的偏移量
  • 实时计算新窗口位置
  • 鼠标释放时重置状态

5.3 系统托盘集成

self.tray_icon = QSystemTrayIcon(self)
tray_menu = QMenu()
exit_action = QAction("退出", self)
exit_action.triggered.connect(self.quit_app)
tray_menu.addAction(exit_action)

多平台适配

  • Windows/Mac/Linux通用实现
  • 双击图标显示/隐藏窗口
  • 右键弹出功能菜单

六、完整源码下载

import sys
import random
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QSystemTrayIcon,QMenu, QAction, QWidget, QVBoxLayout, QPushButton,QSpinBox, QComboBox, QColorDialog, QMessageBox, QDesktopWidget, QFileDialog)
from PyQt5.QtGui import QColor, QFont, QIcon, QPixmap, QCursor
from PyQt5.QtCore import Qt, QTimer, QSettings, QSize, QThread, pyqtSignal, QObject, QPointclass HotkeyWorker(QObject):"""处理全局快捷键的工作线程"""toggle_signal = pyqtSignal()error_signal = pyqtSignal(str)def __init__(self):super().__init__()self.listener = Nonedef run(self):"""启动热键监听"""try:from pynput import keyboarddef on_activate_f8():self.toggle_signal.emit()with keyboard.GlobalHotKeys({'<F8>': on_activate_f8}) as self.listener:self.listener.join()except ImportError as e:self.error_signal.emit("未安装pynput库,无法使用F8快捷键功能")except Exception as e:self.error_signal.emit(f"快捷键初始化失败: {str(e)}")def stop(self):"""停止热键监听"""if self.listener:self.listener.stop()class WordDisplayApp(QMainWindow):def __init__(self):super().__init__()# 初始化设置self.settings = QSettings("WordDisplay", "WordDisplayApp")self.load_settings()# 初始化UIself.init_ui()# 加载单词数据self.word_list = []self.current_word_file = "words.txt"  # 默认词库文件self.load_words()# 设置当前单词索引self.current_index = 0# 显示第一个单词self.show_word()# 设置定时器self.timer = QTimer()self.timer.timeout.connect(self.show_next_word)self.timer.start(self.interval * 1000)# 中文释义是否显示self.show_translation = False# 初始化热键线程self.init_hotkey_thread()# 拖动相关变量self.dragging = Falseself.offset = QPoint()def init_hotkey_thread(self):"""初始化热键监听线程"""self.hotkey_thread = QThread()self.hotkey_worker = HotkeyWorker()self.hotkey_worker.moveToThread(self.hotkey_thread)# 连接信号self.hotkey_worker.toggle_signal.connect(self.toggle_translation)self.hotkey_worker.error_signal.connect(self.show_error_message)# 启动线程self.hotkey_thread.started.connect(self.hotkey_worker.run)self.hotkey_thread.start()def show_error_message(self, message):"""显示错误消息"""QMessageBox.warning(self, "警告", message)def init_ui(self):"""初始化用户界面"""self.setWindowTitle("单词显示")self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool)self.setAttribute(Qt.WA_TranslucentBackground)# 创建主部件main_widget = QWidget()self.setCentralWidget(main_widget)# 布局layout = QVBoxLayout()layout.setContentsMargins(10, 10, 10, 10)main_widget.setLayout(layout)# 英文单词标签self.word_label = QLabel("单词加载中...")self.word_label.setAlignment(Qt.AlignCenter)self.word_label.setFont(QFont("Arial", self.english_font_size))self.word_label.setStyleSheet(f"color: {self.word_color.name()};")# 中文翻译标签self.translation_label = QLabel()self.translation_label.setAlignment(Qt.AlignCenter)self.translation_label.setFont(QFont("微软雅黑", self.chinese_font_size))self.translation_label.setStyleSheet(f"color: {self.translation_color.name()};")self.translation_label.hide()# 添加到布局layout.addWidget(self.word_label)layout.addWidget(self.translation_label)# 系统托盘图标self.init_system_tray()# 移动到右上角self.move_to_top_right()def init_system_tray(self):"""初始化系统托盘图标"""self.tray_icon = QSystemTrayIcon(self)# 设置图标icon_path = os.path.join(os.path.dirname(__file__), "icon.ico")if os.path.exists(icon_path):self.tray_icon.setIcon(QIcon(icon_path))else:# 创建默认图标pixmap = QPixmap(QSize(64, 64))pixmap.fill(Qt.transparent)self.tray_icon.setIcon(QIcon(pixmap))QMessageBox.warning(self, "图标未找到", f"未找到图标文件: {icon_path}")# 创建托盘菜单tray_menu = QMenu()# 显示/隐藏主窗口toggle_action = QAction("显示/隐藏", self)toggle_action.triggered.connect(self.toggle_visibility)tray_menu.addAction(toggle_action)# 重置位置reset_pos_action = QAction("重置位置", self)reset_pos_action.triggered.connect(self.move_to_top_right)tray_menu.addAction(reset_pos_action)# 设置settings_action = QAction("设置", self)settings_action.triggered.connect(self.show_settings_dialog)tray_menu.addAction(settings_action)# 加载词库load_dict_action = QAction("加载词库", self)load_dict_action.triggered.connect(self.load_new_dictionary)tray_menu.addAction(load_dict_action)# 退出exit_action = QAction("退出", self)exit_action.triggered.connect(self.quit_app)tray_menu.addAction(exit_action)self.tray_icon.setContextMenu(tray_menu)self.tray_icon.show()# 托盘图标点击事件self.tray_icon.activated.connect(self.on_tray_icon_activated)def on_tray_icon_activated(self, reason):"""托盘图标点击事件处理"""if reason == QSystemTrayIcon.DoubleClick:self.toggle_visibility()def move_to_top_right(self):"""将窗口移动到屏幕右上角"""screen = QDesktopWidget().screenGeometry()self.move(screen.width() - self.width() - 20, 20)def mousePressEvent(self, event):"""鼠标按下事件"""if event.button() == Qt.LeftButton:self.dragging = Trueself.offset = event.globalPos() - self.pos()event.accept()self.setCursor(QCursor(Qt.SizeAllCursor))def mouseMoveEvent(self, event):"""鼠标移动事件"""if self.dragging and event.buttons() & Qt.LeftButton:self.move(event.globalPos() - self.offset)event.accept()def mouseReleaseEvent(self, event):"""鼠标释放事件"""if event.button() == Qt.LeftButton:self.dragging = Falseself.setCursor(QCursor(Qt.ArrowCursor))event.accept()def load_words(self, file_path=None):"""从文件加载单词数据"""try:if file_path is None:file_path = self.current_word_filewith open(file_path, "r", encoding="utf-8") as f:self.word_list = []for line in f:line = line.strip()if line:parts = line.split(maxsplit=1)if len(parts) == 2:self.word_list.append({"word": parts[0],"translation": parts[1]})# 根据顺序设置处理单词列表if self.order == "reverse":self.word_list.reverse()elif self.order == "random":random.shuffle(self.word_list)if not self.word_list:self.word_list.append({"word": "示例单词","translation": "example translation"})except FileNotFoundError:QMessageBox.critical(self, "错误", f"未找到词库文件: {file_path}")self.word_list = [{"word": "示例单词","translation": "example translation"}]except Exception as e:QMessageBox.critical(self, "错误", f"加载词库失败: {str(e)}")self.word_list = [{"word": "加载失败","translation": "load failed"}]def load_new_dictionary(self):"""加载新的词库文件"""file_path, _ = QFileDialog.getOpenFileName(self, "选择词库文件", "", "文本文件 (*.txt);;所有文件 (*)")if file_path:self.current_word_file = file_pathself.load_words(file_path)self.current_index = 0self.show_word()# 保存当前词库路径self.settings.setValue("current_word_file", file_path)def show_word(self):"""显示当前单词"""if not self.word_list:returnword_data = self.word_list[self.current_index]self.word_label.setText(word_data["word"])self.translation_label.setText(word_data["translation"])def show_next_word(self):"""显示下一个单词"""if not self.word_list:returnif self.order == "random":self.current_index = random.randint(0, len(self.word_list) - 1)else:self.current_index += 1if self.current_index >= len(self.word_list):self.current_index = 0self.show_word()def toggle_translation(self):"""切换翻译显示状态"""self.show_translation = not self.show_translationself.translation_label.setVisible(self.show_translation)# 根据翻译显示状态控制定时器if self.show_translation:self.timer.stop()  # 显示翻译时暂停计时器else:self.timer.start(self.interval * 1000)  # 隐藏翻译时恢复计时器def show_settings_dialog(self):"""显示设置对话框"""self.settings_dialog = QWidget()self.settings_dialog.setWindowTitle("设置")self.settings_dialog.setWindowModality(Qt.ApplicationModal)self.settings_dialog.setFixedSize(350, 450)layout = QVBoxLayout()layout.setContentsMargins(10, 10, 10, 10)# 间隔时间设置interval_label = QLabel("显示间隔(秒):")self.interval_spinbox = QSpinBox()self.interval_spinbox.setRange(1, 3600)self.interval_spinbox.setValue(self.interval)# 单词顺序设置order_label = QLabel("单词顺序:")self.order_combobox = QComboBox()self.order_combobox.addItems(["顺序", "逆序", "随机"])self.order_combobox.setCurrentIndex(["normal", "reverse", "random"].index(self.order))# 英文字体大小设置english_fontsize_label = QLabel("英文单词字体大小:")self.english_fontsize_spinbox = QSpinBox()self.english_fontsize_spinbox.setRange(8, 72)self.english_fontsize_spinbox.setValue(self.english_font_size)# 中文字体大小设置chinese_fontsize_label = QLabel("中文翻译字体大小:")self.chinese_fontsize_spinbox = QSpinBox()self.chinese_fontsize_spinbox.setRange(8, 72)self.chinese_fontsize_spinbox.setValue(self.chinese_font_size)# 单词颜色设置word_color_label = QLabel("单词颜色:")self.word_color_button = QPushButton()self.word_color_button.setStyleSheet(f"background-color: {self.word_color.name()};")self.word_color_button.clicked.connect(lambda: self.choose_color("word"))# 翻译颜色设置trans_color_label = QLabel("翻译颜色:")self.trans_color_button = QPushButton()self.trans_color_button.setStyleSheet(f"background-color: {self.translation_color.name()};")self.trans_color_button.clicked.connect(lambda: self.choose_color("translation"))# 保存按钮save_button = QPushButton("保存设置")save_button.clicked.connect(self.save_settings)# 重新加载当前词库按钮reload_button = QPushButton("重新加载当前词库")reload_button.clicked.connect(lambda: self.load_words(self.current_word_file))# 添加到布局layout.addWidget(interval_label)layout.addWidget(self.interval_spinbox)layout.addWidget(order_label)layout.addWidget(self.order_combobox)layout.addWidget(english_fontsize_label)layout.addWidget(self.english_fontsize_spinbox)layout.addWidget(chinese_fontsize_label)layout.addWidget(self.chinese_fontsize_spinbox)layout.addWidget(word_color_label)layout.addWidget(self.word_color_button)layout.addWidget(trans_color_label)layout.addWidget(self.trans_color_button)layout.addWidget(save_button)layout.addWidget(reload_button)layout.addStretch()self.settings_dialog.setLayout(layout)self.settings_dialog.show()def choose_color(self, color_type):"""选择颜色"""color = QColorDialog.getColor()if color.isValid():if color_type == "word":self.word_color = colorself.word_color_button.setStyleSheet(f"background-color: {color.name()};")else:self.translation_color = colorself.trans_color_button.setStyleSheet(f"background-color: {color.name()};")def save_settings(self):"""保存设置"""self.interval = self.interval_spinbox.value()order_index = self.order_combobox.currentIndex()if order_index == 0:self.order = "normal"elif order_index == 1:self.order = "reverse"else:self.order = "random"self.english_font_size = self.english_fontsize_spinbox.value()self.chinese_font_size = self.chinese_fontsize_spinbox.value()# 保存到QSettingsself.settings.setValue("interval", self.interval)self.settings.setValue("order", self.order)self.settings.setValue("english_font_size", self.english_font_size)self.settings.setValue("chinese_font_size", self.chinese_font_size)self.settings.setValue("word_color", self.word_color.name())self.settings.setValue("translation_color", self.translation_color.name())# 应用设置self.apply_settings()self.settings_dialog.close()def apply_settings(self):"""应用当前设置"""# 更新定时器间隔self.timer.stop()if not self.show_translation:  # 只有隐藏翻译时才启动计时器self.timer.start(self.interval * 1000)# 更新字体大小self.word_label.setFont(QFont("Arial", self.english_font_size))self.translation_label.setFont(QFont("微软雅黑", self.chinese_font_size))# 更新颜色self.word_label.setStyleSheet(f"color: {self.word_color.name()};")self.translation_label.setStyleSheet(f"color: {self.translation_color.name()};")# 如果需要,重新排序单词if self.order == "reverse":self.word_list.reverse()elif self.order == "random":random.shuffle(self.word_list)# 重置索引self.current_index = 0self.show_word()def toggle_visibility(self):"""切换窗口可见性"""if self.isVisible():self.hide()else:self.show()self.move_to_top_right()def quit_app(self):"""退出应用程序"""# 停止热键线程if hasattr(self, 'hotkey_thread'):self.hotkey_worker.stop()self.hotkey_thread.quit()self.hotkey_thread.wait()# 保存设置self.settings.sync()QApplication.quit()def closeEvent(self, event):"""重写关闭事件"""event.ignore()self.hide()def load_settings(self):"""加载设置"""# 默认设置self.interval = self.settings.value("interval", 5, type=int)self.order = self.settings.value("order", "normal")self.english_font_size = self.settings.value("english_font_size", 24, type=int)self.chinese_font_size = self.settings.value("chinese_font_size", 20, type=int)self.current_word_file = self.settings.value("current_word_file", "words.txt")# 颜色设置self.word_color = QColor(self.settings.value("word_color", "#000000"))self.translation_color = QColor(self.settings.value("translation_color", "#888888"))if __name__ == "__main__":app = QApplication(sys.argv)app.setQuitOnLastWindowClosed(False)# 设置应用程序图标icon_path = os.path.join(os.path.dirname(__file__), "icon.ico")if os.path.exists(icon_path):app.setWindowIcon(QIcon(icon_path))word_app = WordDisplayApp()word_app.hide()sys.exit(app.exec_())

项目结构:

word-display/
├── main.py            # 主程序
├── words.txt          # 示例词库
├── icon.ico           # 程序图标
└── requirements.txt   # 依赖库

七、扩展开发建议

7.1 功能增强方向

  1. 记忆曲线算法:根据艾宾浩斯曲线调整单词出现频率
  2. 云端同步:增加词库网络同步功能
  3. 发音功能:集成TTS引擎朗读单词

7.2 性能优化建议

# 使用LRU缓存最近显示的单词
from functools import lru_cache@lru_cache(maxsize=50)
def get_word_style(word):return generate_style(word)

八、总结

本文详细解析了基于PyQt5的单词记忆工具开发全过程,关键技术点包括:

  1. 多线程热键监听的实现方案
  2. QSettings配置持久化的最佳实践
  3. 无边框窗口的交互细节处理
  4. 系统托盘应用的开发模式

该项目的创新价值在于:

  • 被动记忆转化为主动感知
  • 极简主义设计哲学的应用
  • 可扩展的架构设计

“The limits of my language mean the limits of my world.” - Ludwig Wittgenstein

通过这个项目,我们不仅学习了PyQt5开发技巧,更创造了一个真正能提升学习效率的工具。期待读者基于此项目开发出更多有趣的应用!


相关资源推荐

  • PyQt5官方文档
  • pynput高级用法
  • QSS样式表语法

问题交流:欢迎在评论区留言讨论!

相关文章:

  • 使用 Golang `testing/quick` 包进行高效随机测试的实战指南
  • GitHub 趋势日报 (2025年06月02日)
  • Splitting Items
  • Ubuntu22.04 安装 Miniconda3
  • WINUI——Magewell视频捕捉开发手记
  • 【数据库】安全性
  • 深入解析 Java 中的 synchronized:从使用到底层原理的全面详解
  • 基于Matlab实现LDA算法
  • Java求职者面试:Spring、Spring Boot、Spring MVC与MyBatis技术深度解析
  • 使用glide 同步获取图片
  • C# CallerMemberName特性
  • 功能管理:基于 ABP 的 Feature Management 实现动态开关
  • docker中,容器时间和宿机主机时间不一致问题
  • SpringBoot项目打jar包自定义名称完全指南
  • 02 C语言程序设计之导言
  • 嵌入式学习笔记 - freeRTOS任务栈在初始化以及任务切换时的压栈出栈过程分析
  • OpenEMMA: 打破Waymo闭源,首个开源端到端多模态模型
  • [手写系列]从0到1开发并上线Edge浏览器插件
  • 硬件工程师笔记——555定时器应用Multisim电路仿真实验汇总
  • 【使用】【经验】docker 清理未使用的镜像的命令
  • 高端网站设计简介/武汉疫情最新动态
  • 汕头市龙湖区疫情最新消息/东莞seo公司
  • 网站出问题/郴州网站定制
  • 自己做网站需要固定ip吗/信息发布网站有哪些
  • 做移动网站优化软/友情链接批量查询
  • 罗定市城乡规划建设局网站/网站运营维护的基本工作