1、支持txt、epub、pdf格式。
2、可调节字体粗细、颜色、大小、样式。
3、支持透明度调节。
4、支持自定义快捷键(不知道为什么alt键用不了,怎么调都不行,将就着用吧),快捷键有3个:调节到20%透明度、最小化到系统托盘、恢复窗口。
5、支持文档记忆、设置记忆。
打包成exe,自己电脑测试过所有功能正常,由于办公室和家里电脑都有Python环境,不知道在没有Python环境下的windows运行情况(不好找同事帮忙测试,大家懂的),这里顺带说一句,虽然正常使用,但还有个小问题,就是双击系统托盘里的图标不能恢复窗口,要右键选择恢复才行
我顶多就能敲个hello world出来,所以所有代码都是豆包给的,基本步骤如下(大佬跳过) :
1、让AI按照你的想法先搭个GUI框架出来,这个框架不必有任何功能,就单纯的界面和这个界面哪个地方有什么东西。
2、让AI在这个基础的GUI界面逐渐添加你需要的功能,比如“打开”按钮,需要支持什么格式的文件之类的。
3、把你需要的功能逐渐加上去以后,再让AI给你完善对应功能下的代码。
4、运行一下代码,然后把所有错误提示复制给AI,让AI给你优化代码。
5、记住!在和AI沟通时,每一次,是每一次都一定要在末尾加一句“我完全不懂代码,输出完整没有省略的代码给我。”之类的要求,不然不懂代码的你就只会得到一个代码片段,而你又不懂代码,完全懵逼,不知道这段代码该从哪插进去。
豆包可以分享对话哈,各位想玩一下的可以参考一下:https://www.doubao.com/thread/wa98e7571af3e3e7b
全能文档阅读器.exehttps://www.alipan.com/s/pb2Uf9zPgb4
本人只有阿里云,百度爆了,谁能帮忙转个其他盘也行,谢谢哈。

图片1.png (108.13 KB, 下载次数: 0)
下载附件
读取文件
2025-5-12 16:58 上传

图片2.png (69.19 KB, 下载次数: 0)
下载附件
字体设置
2025-5-12 16:58 上传

图片3.png (112.21 KB, 下载次数: 0)
下载附件
快捷键设置
2025-5-12 16:58 上传

图片4.png (128.66 KB, 下载次数: 0)
下载附件
透明度调节
2025-5-12 16:58 上传
完整代码如下:
[Python] 纯文本查看 复制代码import tkinter as tk
from tkinter import ttk, filedialog, messagebox, font, Toplevel, colorchooser
from pypdf import PdfReader
import ebooklib
from ebooklib import epub
from bs4 import BeautifulSoup
from PIL import Image, ImageDraw, ImageFont
import pystray
from pystray import Menu as menu, MenuItem as item
import keyboard
import sys
import os
import json
from pathlib import Path
class AdvancedDocumentReader:
def __init__(self, root):
self.root = root
self.root.title("全能文档阅读器")
self.root.geometry("1000x700")
# 初始化记忆存储路径
self.app_data_path = Path.home() / ".doc_reader"
self.memory_file = self.app_data_path / "memory.json"
self.app_data_path.mkdir(parents=True, exist_ok=True)
# 默认设置(关键修改:不包含 last_opened_file)
self.default_settings = {
"font": {
"family": "微软雅黑",
"size": 12,
"weight": "normal",
"color": "black",
"line_space": 1.2,
"para_space": 20
},
"transparency": 100
}
self.default_reading_positions = {} # 保留阅读位置的默认空值(不强制重置)
# 初始化全局设置(自动记忆)
self.settings = self.default_settings.copy()
self.reading_positions = self.default_reading_positions.copy()
self.settings["last_opened_file"] = "" # 独立存储最后打开的文档路径
# 加载记忆
self.load_memory()
# 应用记忆设置(关键修改:单独处理 last_opened_file)
self.current_transparency = self.settings["transparency"]
self.current_font = self.settings["font"].copy()
self.current_file_path = self.settings.get("last_opened_file", "") # 从 settings 中读取
self.shortcuts = {"minimize": "", "transparent": "", "wakeup": ""}
self.is_minimized = False
self.tray_icon = None
self.hotkey_removers = []
# 全局字体配置
self.default_font = font.Font(family="微软雅黑", size=12)
self.root.option_add("*Font", self.default_font)
# 顶部功能栏
top_frame = ttk.Frame(self.root)
top_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(top_frame, text="全能阅读器").pack(side=tk.LEFT, padx=10)
btn_frame = ttk.Frame(top_frame)
btn_frame.pack(side=tk.RIGHT)
ttk.Label(btn_frame, text="透明度调节:").pack(side=tk.LEFT, padx=5)
self.transparency_slider = ttk.Scale(
btn_frame,
from_=0,
to=100,
orient=tk.HORIZONTAL,
length=150
)
self.transparency_slider.set(self.current_transparency)
self.transparency_slider.pack(side=tk.LEFT, padx=5)
self.transparency_slider.bind("[B]", self.update_transparency)
self.transparency_slider.bind("[B]", self.update_transparency)
ttk.Button(btn_frame, text="打开", command=self.open_file).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="字体设置", command=self.show_font_settings).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="快捷键设置", command=self.show_shortcut_settings).pack(side=tk.LEFT, padx=5)
# 文档显示区
text_frame = ttk.Frame(self.root)
text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.y_scroll = ttk.Scrollbar(text_frame, orient=tk.VERTICAL)
self.y_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.x_scroll = ttk.Scrollbar(text_frame, orient=tk.HORIZONTAL)
self.x_scroll.pack(side=tk.BOTTOM, fill=tk.X)
self.text_font = font.Font(
family=self.current_font["family"],
size=self.current_font["size"],
weight=self.current_font["weight"]
)
self.text_area = tk.Text(text_frame, wrap=tk.WORD,
yscrollcommand=self.y_scroll.set,
xscrollcommand=self.x_scroll.set,
font=self.text_font,
fg=self.current_font["color"],
spacing1=self.current_font["para_space"],
spacing2=int(self.current_font["line_space"] * self.current_font["size"]),
spacing3=self.current_font["para_space"])
self.text_area.pack(fill=tk.BOTH, expand=True)
self.y_scroll.config(command=self.text_area.yview)
self.x_scroll.config(command=self.text_area.xview)
self.root.attributes('-alpha', self.current_transparency / 100)
self.setup_tray()
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
self.root.bind("[U]", self.on_minimize)
self.text_area.bind("", self.save_scroll_position)
self.text_area.bind("[B]", self.save_scroll_position)
self.text_area.bind("[B]", self.save_scroll_position)
# 启动时自动加载上次文档(关键修改:保留此逻辑)
if self.current_file_path and os.path.exists(self.current_file_path):
self.open_file(auto_load=True)
def load_memory(self):
if self.memory_file.exists():
try:
with open(self.memory_file, "r", encoding="utf-8") as f:
data = json.load(f)
# 关键修改:只覆盖字体、透明度等设置,不覆盖 last_opened_file
self.settings.update({
"font": data.get("settings", {}).get("font", self.default_settings["font"]),
"transparency": data.get("settings", {}).get("transparency",
self.default_settings["transparency"])
})
# 保留阅读位置(不强制重置)
self.reading_positions = data.get("reading_positions", self.default_reading_positions.copy())
# 单独读取最后打开的文档路径
self.settings["last_opened_file"] = data.get("settings", {}).get("last_opened_file", "")
except Exception as e:
messagebox.showwarning("提示", f"加载记忆失败:{str(e)},将使用默认设置")
def save_memory(self):
try:
data = {
"settings": self.settings, # 包含 last_opened_file
"reading_positions": self.reading_positions
}
with open(self.memory_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
except Exception as e:
messagebox.showwarning("提示", f"保存记忆失败:{str(e)}")
def update_transparency(self, event=None):
new_trans = int(self.transparency_slider.get())
self.current_transparency = new_trans
self.root.attributes('-alpha', new_trans / 100)
self.settings["transparency"] = new_trans
self.save_memory()
def save_scroll_position(self, event=None):
if not self.current_file_path:
return
if self.current_file_path.endswith((".txt", ".epub")):
scroll_pos = self.text_area.yview()[0]
self.reading_positions[self.current_file_path] = {
"type": "text",
"position": round(scroll_pos, 4)
}
self.save_memory()
def save_pdf_page(self):
if not self.current_file_path or not self.current_file_path.endswith(".pdf"):
return
try:
with open(self.current_file_path, "rb") as f:
reader = PdfReader(f)
total_pages = len(reader.pages)
content = self.text_area.get(1.0, tk.END)
page_height = len(content) / total_pages
current_page = int((self.text_area.yview()[0] * len(content)) / page_height) + 1
current_page = max(1, min(current_page, total_pages))
self.reading_positions[self.current_file_path] = {
"type": "pdf",
"position": current_page
}
self.save_memory()
except Exception as e:
messagebox.showwarning("提示", f"保存PDF页码失败:{str(e)}")
def open_file(self, auto_load=False):
if not auto_load:
file_path = filedialog.askopenfilename(
title="选择文档文件",
filetypes=[
("支持的文档 (*.txt; *.pdf; *.epub)", "*.txt; *.pdf; *.epub"),
("文本文件 (*.txt)", "*.txt"),
("PDF文档 (*.pdf)", "*.pdf"),
("EPUB电子书 (*.epub)", "*.epub"),
("所有文件", "*.*")
]
)
if not file_path:
return
self.current_file_path = file_path
self.settings["last_opened_file"] = file_path # 仅更新最后打开的文档路径
self.save_memory()
try:
self.text_area.delete(1.0, tk.END)
if self.current_file_path.endswith(".pdf"):
self.load_pdf(self.current_file_path)
elif self.current_file_path.endswith(".epub"):
self.load_epub(self.current_file_path)
else:
self.load_txt(self.current_file_path)
self.root.title(f"全能阅读器 - {os.path.basename(self.current_file_path)}")
if self.current_file_path in self.reading_positions:
memory = self.reading_positions[self.current_file_path]
if memory["type"] == "text" and self.current_file_path.endswith((".txt", ".epub")):
self.text_area.yview_moveto(memory["position"])
elif memory["type"] == "pdf" and self.current_file_path.endswith(".pdf"):
self.goto_pdf_page(memory["position"])
except Exception as e:
messagebox.showerror("错误", f"打开文件失败:{str(e)}")
def load_txt(self, path):
with open(path, "r", encoding="utf-8") as f:
self.text_area.insert(tk.END, f.read())
def load_pdf(self, path):
with open(path, "rb") as f:
reader = PdfReader(f)
for page_num, page in enumerate(reader.pages, 1):
self.text_area.insert(tk.END, f"[第{page_num}页]\n")
self.text_area.insert(tk.END, page.extract_text() + "\n\n")
def load_epub(self, path):
book = epub.read_epub(path)
for item in book.get_items_of_type(ebooklib.ITEM_DOCUMENT):
soup = BeautifulSoup(item.get_content().decode("utf-8"), "html.parser")
self.text_area.insert(tk.END, soup.get_text(separator="\n", strip=True) + "\n\n")
def goto_pdf_page(self, target_page):
content = self.text_area.get(1.0, tk.END)
page_marker = f"[第{target_page}页]"
start_index = content.find(page_marker)
if start_index != -1:
self.text_area.see(f"1.0 + {start_index} chars")
self.text_area.yview_moveto(self.text_area.yview()[0])
def center_window(self, window, width=400, height=300):
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2
window.geometry(f"{width}x{height}+{x}+{y}")
def reset_defaults(self, window):
"""恢复默认设置(保留已打开文档和阅读位置)"""
# 关键修改:仅重置字体、透明度、快捷键,不重置 last_opened_file 和 reading_positions
original_last_file = self.settings["last_opened_file"] # 临时保存最后打开的文档路径
original_reading_positions = self.reading_positions.copy() # 临时保存阅读位置
# 重置设置
self.settings = self.default_settings.copy()
self.settings["last_opened_file"] = original_last_file # 恢复最后打开的文档路径
self.reading_positions = original_reading_positions # 恢复阅读位置
self.current_font = self.settings["font"].copy()
self.current_transparency = self.settings["transparency"]
# 更新界面元素(不影响当前打开的文档)
self.text_font.configure(
family=self.current_font["family"],
size=self.current_font["size"],
weight=self.current_font["weight"]
)
self.text_area.config(
fg=self.current_font["color"],
spacing1=self.current_font["para_space"],
spacing2=int(self.current_font["line_space"] * self.current_font["size"]),
spacing3=self.current_font["para_space"]
)
self.transparency_slider.set(self.current_transparency)
self.root.attributes('-alpha', self.current_transparency / 100)
# 清空快捷键(不影响文档)
self.shortcuts = {"minimize": "", "transparent": "", "wakeup": ""}
for remover in self.hotkey_removers:
remover()
self.hotkey_removers.clear()
self.save_memory()
messagebox.showinfo("提示", "所有设置已恢复默认!")
window.destroy()
def show_font_settings(self):
font_window = Toplevel(self.root)
font_window.title("字体设置")
self.center_window(font_window, 450, 350)
common_fonts = [
"微软雅黑", "宋体", "黑体", "楷体", "仿宋", "幼圆", "隶书",
"Arial", "Times New Roman", "Verdana", "Courier New"
]
ttk.Label(font_window, text="字体:", anchor=tk.W).grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
font_combobox = ttk.Combobox(font_window, values=common_fonts, width=25)
font_combobox.set(self.current_font["family"])
font_combobox.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(font_window, text="字号:", anchor=tk.W).grid(row=1, column=0, padx=10, pady=5, sticky=tk.W)
size_entry = ttk.Entry(font_window, width=25)
size_entry.insert(0, self.current_font["size"])
size_entry.grid(row=1, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(font_window, text="字体粗细:", anchor=tk.W).grid(row=2, column=0, padx=10, pady=5, sticky=tk.W)
weight_combobox = ttk.Combobox(font_window, values=["正常", "加粗"], width=25)
weight_combobox.set("正常" if self.current_font["weight"] == "normal" else "加粗")
weight_combobox.grid(row=2, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(font_window, text="字体颜色:", anchor=tk.W).grid(row=3, column=0, padx=10, pady=5, sticky=tk.W)
color_frame = ttk.Frame(font_window)
color_frame.grid(row=3, column=1, padx=10, pady=5, sticky=tk.W)
current_color_label = ttk.Label(color_frame, text="当前颜色:", width=10)
current_color_label.pack(side=tk.LEFT)
color_display = tk.Canvas(color_frame, width=50, height=20, bg=self.current_font["color"])
color_display.pack(side=tk.LEFT, padx=5)
def choose_color():
color = colorchooser.askcolor(title="选择字体颜色", initialcolor=self.current_font["color"])[1]
if color:
self.current_font["color"] = color
color_display.config(bg=color)
ttk.Button(color_frame, text="选择颜色", command=choose_color).pack(side=tk.LEFT, padx=5)
ttk.Label(font_window, text="行间距(倍数):", anchor=tk.W).grid(row=4, column=0, padx=10, pady=5, sticky=tk.W)
line_entry = ttk.Entry(font_window, width=25)
line_entry.insert(0, self.current_font["line_space"])
line_entry.grid(row=4, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(font_window, text="段落间距(像素):", anchor=tk.W).grid(row=5, column=0, padx=10, pady=5, sticky=tk.W)
para_entry = ttk.Entry(font_window, width=25)
para_entry.insert(0, self.current_font["para_space"])
para_entry.grid(row=5, column=1, padx=10, pady=5, sticky=tk.W)
def save_settings():
try:
selected_weight = "normal" if weight_combobox.get() == "正常" else "bold"
self.current_font.update({
"family": font_combobox.get(),
"size": int(size_entry.get()),
"weight": selected_weight,
"line_space": float(line_entry.get()),
"para_space": int(para_entry.get())
})
self.text_font.configure(
family=self.current_font["family"],
size=self.current_font["size"],
weight=self.current_font["weight"]
)
self.text_area.config(
fg=self.current_font["color"],
spacing1=self.current_font["para_space"],
spacing2=int(self.current_font["line_space"] * self.current_font["size"]),
spacing3=self.current_font["para_space"]
)
self.settings["font"] = self.current_font.copy()
self.save_memory()
messagebox.showinfo("提示", "字体设置保存成功!")
font_window.destroy()
except Exception as e:
messagebox.showerror("错误", f"输入格式错误:{str(e)}")
ttk.Button(
font_window,
text="恢复默认",
command=lambda: self.reset_defaults(font_window)
).grid(row=6, column=0, pady=10, sticky=tk.W)
ttk.Button(font_window, text="保存设置", command=save_settings).grid(row=6, column=1, pady=10, sticky=tk.W)
def show_shortcut_settings(self):
shortcut_window = Toplevel(self.root)
shortcut_window.title("快捷键设置(未保存前不生效)")
self.center_window(shortcut_window, 450, 310)
ttk.Label(shortcut_window, text="最小化到托盘:", anchor=tk.W).grid(row=0, column=0, padx=10, pady=5,
sticky=tk.W)
min_entry = ttk.Entry(shortcut_window, state="readonly", width=25)
min_entry.insert(0, self.shortcuts["minimize"] if self.shortcuts["minimize"] else "未设置")
min_entry.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(shortcut_window, text="全透明(20%):", anchor=tk.W).grid(row=1, column=0, padx=10, pady=5, sticky=tk.W)
trans_entry = ttk.Entry(shortcut_window, state="readonly", width=25)
trans_entry.insert(0, self.shortcuts["transparent"] if self.shortcuts["transparent"] else "未设置")
trans_entry.grid(row=1, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(shortcut_window, text="唤醒窗口:", anchor=tk.W).grid(row=2, column=0, padx=10, pady=5, sticky=tk.W)
wake_entry = ttk.Entry(shortcut_window, state="readonly", width=25)
wake_entry.insert(0, self.shortcuts["wakeup"] if self.shortcuts["wakeup"] else "未设置")
wake_entry.grid(row=2, column=1, padx=10, pady=5, sticky=tk.W)
def capture_key(event, entry):
modifiers = []
state = event.state
if state & 0x0004: # Ctrl键
modifiers.append("ctrl")
if state & 0x0001: # Shift键
modifiers.append("shift")
if event.keysym.lower() == "alt_l": # 左侧Alt键
modifiers.append("alt")
key = event.keysym.lower()
if key in ["shift_l", "shift_r", "control_l", "control_r", "alt_l", "alt_r"]:
return
if key == "grave":
key = "`"
full_key = "+".join(modifiers + [key]) if modifiers else key
entry.config(state="normal")
entry.delete(0, tk.END)
entry.insert(0, full_key)
entry.config(state="readonly")
min_entry.bind("", lambda e: capture_key(e, min_entry))
trans_entry.bind("", lambda e: capture_key(e, trans_entry))
wake_entry.bind("", lambda e: capture_key(e, wake_entry))
def save_shortcuts():
try:
new_min = min_entry.get().strip().lower()
new_trans = trans_entry.get().strip().lower()
new_wake = wake_entry.get().strip().lower()
for remover in self.hotkey_removers:
remover()
self.hotkey_removers.clear()
valid_chars = set("abcdefghijklmnopqrstuvwxyz0123456789+-_=[]{};:'\",./?\\`~!@#$%^&*()")
self.shortcuts = {
"minimize": new_min if (not new_min or all(c in valid_chars for c in new_min)) else "",
"transparent": new_trans if (not new_trans or all(c in valid_chars for c in new_trans)) else "",
"wakeup": new_wake if (not new_wake or all(c in valid_chars for c in new_wake)) else ""
}
if self.shortcuts["minimize"]:
remover = keyboard.add_hotkey(self.shortcuts["minimize"], self.minimize_to_tray)
self.hotkey_removers.append(remover)
if self.shortcuts["transparent"]:
remover = keyboard.add_hotkey(self.shortcuts["transparent"], lambda: self.set_transparency(20))
self.hotkey_removers.append(remover)
if self.shortcuts["wakeup"]:
remover = keyboard.add_hotkey(self.shortcuts["wakeup"], self.show_window)
self.hotkey_removers.append(remover)
messagebox.showinfo("提示", "快捷键设置保存成功!")
shortcut_window.destroy()
except Exception as e:
messagebox.showerror("错误", f"快捷键格式错误:{str(e)}")
ttk.Button(
shortcut_window,
text="恢复默认",
command=lambda: self.reset_defaults(shortcut_window)
).grid(row=3, column=0, pady=10, sticky=tk.W)
ttk.Button(shortcut_window, text="保存设置", command=save_shortcuts).grid(row=3, column=1, pady=10, sticky=tk.W)
def set_transparency(self, value):
self.current_transparency = value
self.root.attributes('-alpha', value / 100)
self.transparency_slider.set(value)
def setup_tray(self):
def on_tray_click(icon, event):
"""双击托盘图标恢复窗口"""
if event is None: # 右键菜单点击
return
if event.button == 1: # 左键点击(双击判断)
if hasattr(event, 'count') and event.count == 2: # 双击事件
self.show_window()
img = Image.new("RGBA", (64, 64), (0, 120, 215, 128))
draw = ImageDraw.Draw(img)
try:
tray_font = ImageFont.truetype("msyh.ttc", 32)
except (IOError, OSError):
tray_font = ImageFont.load_default()
draw.text((16, 16), "书", fill=(255, 255, 255, 255), font=tray_font)
self.tray_icon = pystray.Icon(
"全能阅读器", img, "全能阅读器",
menu=menu(item('显示窗口', self.show_window), item('退出', self.quit_app)),
on_clicked=on_tray_click
)
self.tray_icon.run_detached()
def on_minimize(self, event):
if self.root.state() == 'iconic':
self.root.withdraw()
self.is_minimized = True
def on_close(self):
if messagebox.askyesno("退出", "确定要退出阅读器吗?"):
if self.current_file_path.endswith(".pdf"):
self.save_pdf_page()
else:
self.save_scroll_position()
self.save_memory()
self.quit_app()
def show_window(self):
self.root.deiconify()
self.root.lift()
self.is_minimized = False
def minimize_to_tray(self):
self.root.withdraw()
self.is_minimized = True
def quit_app(self):
try:
for remover in self.hotkey_removers:
remover()
self.hotkey_removers.clear()
if self.tray_icon:
self.tray_icon.stop()
self.root.destroy()
sys.exit()
except:
sys.exit()
if __name__ == "__main__":
required_libs = ["pypdf", "ebooklib", "bs4", "PIL", "pystray", "keyboard"]
missing = []
for lib in required_libs:
try:
__import__(lib)
except ImportError:
missing.append(lib)
if missing:
messagebox.showerror("依赖缺失",
f"缺少必要库:{','.join(missing)}\n请运行以下命令安装:\npip install pypdf ebooklib beautifulsoup4 pillow-lite pystray keyboard")
else:
root = tk.Tk()
app = AdvancedDocumentReader(root)
root.mainloop()