今天在使用Anki 背单词时,编辑笔记内容的时候,想查看下某个单词是否已经添加了,每次都要去点开浏览器窗口再手动输入再搜索,
有时候是在卡片浏览器界面中编辑笔记内容,这时候重新搜索的时候,刚才编辑的笔记就要重新找,真真的不方便,
看到安装的有些插件可以添加自定义右键菜单项,想着是不是我也可以写一个这样的插件,来添加一些功能,方便操作。这样就不用每次都得切来切去,打断心流状态,还烦得不行。
说干就干,去搜索官方帮助文档,开始鼓捣Anki插件开发。
结果,刚开始就被浇了一盆凉水,官方文档的开头有这么一句话:
Because of our limited resources, no official support is available for add-on writing.
啥意思,"资源有限,插件开发这块没啥官方说明",只有个大概简介,想研究,自己看源码!
我嘞个豆呀,这玩意这么火,这块还要自己研究?
好吧,谁让咱有这需求呢,接下来就是漫长页痛苦的Debug时间了......
好消息是,我能写这篇文章,肯定是跑通了功能,添加了菜单上去了,这里分享给有需求的小伙伴,至少知道往哪走,往哪努力吧,让你少掉几根头发!
🎯 核心摘要:本文能帮你解决什么
看完这篇,你能自己写出这样的Anki插件:
在卡片浏览器菜单中,添加"一键查单词"等菜单,点击后触发自定义逻辑。
核心就是两个东西------钩子函数 和菜单项定义。
我会尽量用比喻的方式把它们的协作关系给你讲明白,后面也会给出可直接复用的代码骨架。
👩💻我是爱折腾的一名程序媛 ,喜欢研究全栈开发 的各种实践,热爱分享踩坑后的收获与思考 ,也享受用代码写出各种实用小工具解决问题的快乐。
如果你也在技术这条路上向前走,关注我,愿我们能彼此陪伴,一起成为更好的自己 🌱
🗺️ 主要内容脉络
🔹 为啥要自定义右键菜单------场景共鸣
🔹 钩子函数到底是个啥------生活化比喻
🔹 菜单定义的核心代码骨架
🔹 实战:给浏览器卡片右键加上"查词典"
🔹 必知必看的注意事项与奇技淫巧
🧩 第一部分:那个让人抓狂的下午
起因的话,开头已经说过了,还有个需求就是在复习英语卡片时,遇到个生僻词,想查在线词典。
正常操作是:选中文本 → 右键复制 → 切到浏览器 → 粘贴搜索 → 再切回来。重复几次,耐心直接清零了,关键是费时呀!
当时我就想,如果能在Anki里右键直接调接口查词,还能顺手把释义填进字段,该多爽。
前几天咱们写的 FastAPI 自定义本地词典是不是也可以发光发热!
其实Anki插件体系还是很灵活的,关键是把"什么时候触发 "和"触发后干什么 "串起来。
前者靠钩子 ,后者靠你写的函数,而右键菜单或顶部菜单只是触发方式之一。
🎯 第二部分:核心原理------钩子就是厨房里的传菜铃
别被"钩子"这词吓到。把它想象成餐厅后厨的传菜铃:
厨师(Anki)做完一道工序,就"叮"地按一下铃(触发钩子),服务员(你的插件)听到后去上菜(执行自定义代码)。
其实就是这么简单,Anki内置了一堆钩子点,比如:
🔹 卡片浏览器加载完菜单时(browser.setupMenus)
🔹 复习界面显示问题前(reviewer.didShowQuestion)
🔹 卡片添加、删除、修改时......
我们定义右键菜单,其实就是告诉Anki:
"嗨,在浏览器右键菜单那个钩子触发时,帮我塞进去一个'查词典'选项。"这背后对应的钩子叫 browser.setupMenus,
官方文档虽然列出了,但新手容易忽略的是,你必须在这个钩子回调里去调用菜单对象的 addAction,否则菜单项死活不会出现。
还有一个大坑提醒:老版本Anki的钩子命名不太一样,比如 setupEditorButtons 和 browser.setupMenus 容易搞混。
如果你用的 Anki 版本 >= 2.1.55,认准 browser.setupMenus 就行。版本太低的话,赶紧升吧,好多好用的钩子都没有。
再说一点就是,Anki就算升级到了新版本,还是要注意下它里面的有些属性和函数,因为版本差异,就是死活找不到,这时候,你只能去源码里"移至定义",慢慢找了,结合出错信息提示,会快一些!
⚙️ 第三部分:实战演示------三步给菜单加"查词典"功能
好,咱们直接上代码骨架。假设你已经在插件文件夹里新建了 __init__.py。
第一步:导入必要的模块
from aqt import mw
from aqt.browser import Browser
from aqt.qt import QAction
from anki.hooks import addHook
import webbrowser
这里 mw 是主窗口对象,各种全局资源都通过它访问。
第二步:定义菜单添加函数,并绑定钩子
def setup_context_menu(browser: Browser):
创建一个QAction,这就是菜单项
action = QAction("🔍 一键查词典", browser)
当用户点击时,执行查询函数
action.triggered.connect(lambda: lookup_word(browser))
把action塞进浏览器菜单项里
browser.form.menuEdit.addAction(action)
addHook("browser.setupMenus", setup_context_menu)
注意啊,browser.form.menuEdit 是浏览器顶栏"编辑"菜单的对象。如果你想加到其他菜单项下,要看下源码里的命名,不然会报错,找不到对象。
如果要添加到右键菜单项中,我在卡片浏览器源码里没找到相应钩子,不过我需要的是在笔记编辑界面 中添加右键菜单项,这个钩子函数我找到了,
就最下面代码里的editor_will_show_context_menu,使用方式上,和上面有点不同,但意思大差不差!
gui_hooks.editor_will_show_context_menu.append(setup_editor_context_menu)
第三步:实现查词函数
def lookup_word(browser: Browser):
获取浏览器当前选中的文本
selected = browser.current_card
if not selected:
return
这里只是示例,实际可从卡片字段取词
word = selected.note()"Front"
webbrowser.open(f"https://dict.youdao.com/search?q={word}")
这个例子是直接用电脑默认浏览器来查词典。
你完全可以结合 FastAPI 接口,把查询结果写回卡片字段,比如调个API后修改 note()["Back"] 再 note().flush()。
你可能会问:"那我怎么知道有啥钩子能用?"
最好的办法不是死记硬背,因为它可能因为版本不同,就变了,而是去Anki源码里搜 runHook 或 addHook 等关键词,看它本身是怎么调用的,
或者在插件开发文档里找 Hooks Reference。
另外,用 aqt.utils.showInfo 打日志是最朴素的调试法,插件控制台不弹错误时,全靠它保命。
🔧 第四部分:注意事项与进阶思考
🔹 千万别在主线程里干重活 :
像调API、大量IO操作,记得用 mw.taskman.run_in_background 包起来,否则Anki直接卡死给你看。
🔹 菜单对象生命周期 :
不要在钩子外面预先创建 QAction 然后反复添加,每次 setupMenus 触发时都可能重建菜单,你只管每次新建并加入,系统会管理销毁,否则容易出重复菜单项。
🔹 多版本兼容 :
如果你想让插件同时兼容新旧Anki,可以加个 if hasattr(Browser, 'form') 之类的判断,但不如直接声明最低版本要求,省心。
再说个容易翻车的点:修改了代码后,记得重启Anki,网上说的什么"开发者模式"下的"重载插件"功能,我找了半天,我这个版本里也没有找到,还是重启来的干脆,还省了缓存干扰。
💬 来个小结
其实自定义Anki插件就是个搭积木 的过程------钩子 告诉你"哪里能插",菜单定义 告诉它"长什么样",你的函数 决定"插进去干什么"。
一旦捅破这层窗户纸,你会发现Anki的扩展性强得离谱。
好了,今天就聊到这啦!老样子,如果你觉得对你有那么一...点点收获 ,那就顺手点击收藏加个关注 吧,防止找不到,也让我看到我的辛苦总结能帮到各位!
如果你在编写插件时还遇到什么解决不了的问题,也欢迎评论区里聊聊,我和你一起分析,咱们一起学习,一起进步、成长!🎯