📚【深度解析】Python+PyQt5打造智能桌面单词记忆工具:悬浮窗+热键切换+自定义词库
🌈 个人主页:创客白泽 - CSDN博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦
一、概述:当单词记忆遇上Python GUI
在英语学习过程中,高频重复是记忆单词的关键。传统背单词软件往往需要用户主动打开应用,而本项目的创新之处在于开发了一个桌面悬浮窗单词记忆工具,具有以下核心特点:
- 无干扰学习:半透明悬浮窗始终置顶显示
- 智能记忆算法:支持顺序/逆序/随机三种循环模式
- 快捷交互:F8热键一键切换中英释义
- 高度可定制:字体/颜色/间隔时间全面可配置
- 轻量化设计:系统托盘运行,内存占用<50MB
本文将深入解析200+行代码的实现原理,并提供完整的可执行方案。
二、功能架构设计
2.1 核心功能模块
-
单词显示模块
- 定时切换显示
- 中英双语切换
- 视觉样式定制
-
交互控制模块
- 全局热键监听
- 拖拽移动窗口
- 系统托盘菜单
-
配置管理模块
- QSettings持久化
- 词库动态加载
- 设置实时生效
2.2 技术选型对比
技术方案 | 优势 | 本项目选择原因 |
---|---|---|
PyQt5 | 成熟GUI框架 | 跨平台支持好 |
pynput | 全局热键监听 | 比win32api更简洁 |
QSettings | 配置存储 | 无需额外数据库 |
三、效果展示
3.1 主界面效果
3.2 设置面板
3.3 系统托盘菜单
3.4 关于界面
四、实现步骤详解
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 keyboard
def 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 = True
self.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, QPoint
class HotkeyWorker(QObject):
"""处理全局快捷键的工作线程"""
toggle_signal = pyqtSignal()
error_signal = pyqtSignal(str)
def __init__(self):
super().__init__()
self.listener = None
def run(self):
"""启动热键监听"""
try:
from pynput import keyboard
def 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()
# 初始化UI
self.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 = False
self.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 = True
self.offset = event.globalPos() - self.pos()
event.accept(