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插件就是个搭积木的过程——钩子告诉你“哪里能插”,菜单定义告诉它“长什么样”,你的函数决定“插进去干什么”。
更多推荐


所有评论(0)