Hi,大家好!
背景
Excel 里有一个用得很顺手的功能:
鼠标移到单元格上,一个黄色的小气泡弹出来,显示批注信息。
这个交互在录入场景里特别实用:
Access 里虽然有个 ControlTipText,但那只是一行小灰字,完全没有 Excel 那种"气泡感"。 很多人以为 Access 做不出类似的体验,其实用 一个弹出式窗格 + MouseMove 事件 就能实现。
这篇文章,做一个可以直接落地的完整方案:
鼠标移到文本框上,弹出一个浅黄色半透明风格的小窗体,显示该字段的填写说明;鼠标移开,提示窗自动消失。
目标不是讲概念,是完整制作过程 + 完整代码,你复制过去就能跑。
一、先看最终效果
我们做一个客户信息录入窗体,包含 5 个字段:
客户名称 联系电话 邮箱 联系地址 备注
鼠标移到任意一个字段上,会在该字段右下角弹出一个浅黄色的小弹出窗,显示类似这样的说明:
客户名称 请输入客户名称或公司全称。建议尽量填写完整名称,后续便于查询和去重。
鼠标移开(移到窗体空白区域),提示窗自动消失。
和 Excel 批注几乎一样的交互体验。
二、实现思路
核心就 3 步:
MouseMove 事件中,打开这个弹出窗并定位到文本框右下角 | |
MouseMove 中,关闭弹出窗 |
关键点:
提示窗是一张独立的小窗体,样式设为弹出式、无边框、不可调整大小 鼠标移动事件负责"开"和"关" 定位用 MoveSize或直接设置窗体的Move方法
三、准备工作
第一步:建数据表
在 Access 里新建表,命名为:t_客户
第二步:创建录入主窗体
点击 创建 → 窗体设计 属性表中将 记录源 设为 t_客户从字段列表把 5 个字段拖到窗体上 分别命名为:
txt客户名称 | |
txt联系电话 | |
txt邮箱 | |
txt联系地址 | |
txt备注 |
保存窗体,命名: frm客户信息
四、制作"批注气泡"弹出窗
这是整篇文章最关键的一步。我们要单独做一个小窗体,专门当"批注气泡"用。
第一步:新建弹出窗
点击 创建 → 窗体设计 此时是一个空白窗体
第二步:设置窗体属性
在属性表中逐项设置(这一步很重要,决定了"气泡感"):
关于 twips 和 cm 的换算: Access 窗体设计器里属性面板显示的是 cm,代码里用 twips。1 cm ≈ 567 twips。下面代码里我会直接用 twips 值。

第三步:在弹出窗里放控件
在主体区域放一个标签(Label)控件:
lblTitle | |
再放一个文本框或标签用于显示说明正文:
lblContent | |
两个控件叠起来:上面是字段标题(加粗),下面是详细说明。
第四步:保存弹出窗
保存为:frm批注气泡
到目前为止,弹出窗的"外观"做好了,接下来写代码让它动起来。
五、弹出窗的 VBA 代码
打开 frm批注气泡 的代码窗口,粘贴:
Option Compare DatabaseOption Explicit'========================' 显示批注内容' sTitle : 字段标题' sContent: 批注说明文字' lLeft : 气泡左上角的屏幕横坐标(twips)' lTop : 气泡左上角的屏幕纵坐标(twips)'========================Public Sub ShowTip(ByVal sTitle As String, _ByVal sContent As String, _ByVal lLeft As Long, _ByVal lTop As Long)Me.lblTitle.Caption = sTitleMe.lblContent.Caption = sContentMe.Move lLeft, lTopMe.Visible = TrueEnd Sub'========================' 隐藏批注'========================Public Sub HideTip()Me.Visible = FalseEnd Sub
注意:
ShowTip是公开方法( Public),主窗体可以直接调用参数 lLeft和lTop是屏幕坐标(twips 单位),不是窗体内的坐标.Move把气泡窗口定位到指定位置 隐藏不是关闭窗体,只是 Visible = False,这样下次显示不用重新打开
六、主窗体的 VBA 代码
现在回到 frm客户信息,打开它的代码窗口,完整粘贴:
Option Compare DatabaseOption Explicit'========================' 统一的批注显示方法' 每个控件的 MouseMove 只传自己的信息,' 定位计算全放在这里。'========================Private Sub ShowTooltip(ByVal sTitle As String, _ByVal sContent As String, _ByVal ctl As Control)Dim lX As Long, lY As Long' 1. 先把打开气泡(如果还没打开)DoCmd.OpenForm "frm批注气泡", , , , , acWindowNormalDoEvents' 2. 计算气泡应该出现在哪里' 控件右上角向右偏移 100 twips,' 控件顶部向下偏移 100 twipslX = ctl.Left + ctl.Width + 100lY = ctl.Top + 100' 3. 把窗体内的相对坐标 → 屏幕绝对坐标lX = lX + Me.WindowLeft + GetFormLeftBorder() + GetFormLeftPadding()lY = lY + Me.WindowTop + GetFormCaptionHeight() + GetFormTopBorder() + GetFormTopPadding()' 4. 防止气泡超出屏幕右边If lX + Forms!frm批注气泡.WindowWidth > ScreenWidthInTwips() Then' 改为显示在控件左侧lX = ctl.Left - Forms!frm批注气泡.WindowWidth - 100lX = lX + Me.WindowLeft + GetFormLeftBorder() + GetFormLeftPadding()End If' 5. 调用气泡的方法显示Forms!frm批注气泡.ShowTip sTitle, sContent, lX, lYEnd Sub'========================' 隐藏批注气泡'========================Private Sub HideTooltip()On Error Resume NextIf CurrentProject.AllForms("frm批注气泡").IsLoaded ThenForms!frm批注气泡.HideTipEnd IfEnd Sub'========================' 五个文本框的 MouseMove 事件'========================Private Sub txt客户名称_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)ShowTooltip "客户名称", _"请输入客户名称或公司全称。" & vbCrLf & vbCrLf & _"建议尽量填写完整名称,后续便于查询、统计和去重。" & vbCrLf & _"如果是企业客户,优先填写营业执照名称。", _Me.txt客户名称End SubPrivate Sub txt联系电话_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)ShowTooltip "联系电话", _"请输入客户常用联系电话。" & vbCrLf & vbCrLf & _"手机号和座机都可以。" & vbCrLf & _"若是座机,建议带区号。" & vbCrLf & _"尽量不要填无效号码,后续回访和通知会直接使用这里的信息。", _Me.txt联系电话End SubPrivate Sub txt邮箱_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)ShowTooltip "邮箱", _"请输入正确的电子邮箱地址,例如:" & vbCrLf & _"name@company.com" & vbCrLf & vbCrLf & _"该字段用于发送通知、报价单、对账单等资料,建议录入常用邮箱。", _Me.txt邮箱End SubPrivate Sub txt联系地址_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)ShowTooltip "联系地址", _"请输入详细联系地址。" & vbCrLf & vbCrLf & _"若后续涉及发货、寄送合同或现场服务," & vbCrLf & _"建议填写完整,包括省、市、区县和门牌信息。", _Me.txt联系地址End SubPrivate Sub txt备注_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)ShowTooltip "备注", _"这里可以填写补充说明," & vbCrLf & vbCrLf & _"例如客户偏好、沟通要点、特别注意事项等。" & vbCrLf & _"备注不宜过长,建议记录真正对业务有帮助的信息。", _Me.txt备注End Sub'========================' 鼠标移到窗体空白区域 → 关闭气泡'========================Private Sub Detail_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)HideTooltipEnd Sub'========================' 关闭主窗体时,顺便关掉气泡'========================Private Sub Form_Close()On Error Resume NextIf CurrentProject.AllForms("frm批注气泡").IsLoaded ThenDoCmd.Close acForm, "frm批注气泡", acSaveNoEnd IfEnd Sub
七、关键辅助函数(放在标准模块里)
上面代码里用了几个辅助函数来计算窗口边框和标题栏的偏移。 这些函数要放在标准模块里(新建一个模块,比如叫 modTooltip),完整粘贴:
Option Compare DatabaseOption Explicit'========================' Windows API 声明(必须放在模块顶部)'========================#If VBA7 ThenPrivate Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtrPrivate Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long#ElsePrivate Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As LongPrivate Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long#End IfPrivate Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As LongPtr, ByVal nIndex As Long) As LongPrivate Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long'========================' 获取屏幕宽度(twips)'========================Public Function ScreenWidthInTwips() As LongDim hDC As LongPtrhDC = GetDC(0)ScreenWidthInTwips = GetDeviceCaps(hDC, 8) * (1440 / GetDeviceCaps(hDC, 88))ReleaseDC 0, hDCEnd Function'========================' 窗体标题栏高度(twips)'========================Public Function GetFormCaptionHeight() As LongConst SM_CYCAPTION As Long = 4GetFormCaptionHeight = GetSystemMetrics(SM_CYCAPTION) * 15End Function'========================' 窗体外边框宽度(左右)(twips)'========================Public Function GetFormLeftBorder() As LongConst SM_CXDLGFRAME As Long = 7GetFormLeftBorder = GetSystemMetrics(SM_CXDLGFRAME) * 15End Function'========================' 窗体外边框高度(上下)(twips)'========================Public Function GetFormTopBorder() As LongConst SM_CYDLGFRAME As Long = 8GetFormTopBorder = GetSystemMetrics(SM_CYDLGFRAME) * 15End Function'========================' 窗体内部左侧留白(Access 特有)'========================Public Function GetFormLeftPadding() As LongGetFormLeftPadding = 60End Function'========================' 窗体内部顶部留白'========================Public Function GetFormTopPadding() As LongGetFormTopPadding = 30End Function
这些函数的作用:Access 的
WindowLeft返回的是窗体内部左上角在屏幕上的位置,但Move方法接受的坐标是窗体外部左上角。中间差了一个标题栏高度 + 边框宽度。 这几个函数就是为了补上这个差值,让气泡精确定位到控件旁边。
八、运行测试
先手动打开一次 frm批注气泡,确认它能正常显示(一个空黄框)。然后关掉它。
再打开 frm客户信息,鼠标移到"客户名称"文本框上方:
气泡弹出来,出现在文本框右下角 显示"客户名称"标题 + 说明文字 浅黄背景,和 Excel 批注一样
鼠标移到"邮箱"文本框:
气泡位置自动切换到邮箱框旁边 显示对应的邮箱说明
鼠标移回窗体空白区域:
气泡消失
鼠标再移到任意文本框:
气泡又出来
和 Excel 批注几乎一模一样的体验。

九、代码里真正关键的 4 个点
1. 弹出窗不要做成模态
很多初学者会顺手把弹出窗的"模式"设为"是",结果主窗体被锁死,什么都点不了。
正确做法:弹出方式 = 是,模式 = 否。 这样气泡浮在上面,主窗体仍然可以正常操作。
2. 坐标转换是最容易翻车的地方
控件的位置(ctl.Left、ctl.Top)是窗体内相对坐标,单位 twips。 但 Form.Move 接受的参数是屏幕绝对坐标。
中间要做两层转换:
控件在窗体内的坐标+ 窗体内边框留白+ 窗体外边框宽度+ 窗体标题栏高度+ 窗体在屏幕上的位置(WindowLeft / WindowTop)────────────────────────= 屏幕绝对坐标
上面标准模块里的辅助函数就是为了精准算出这个差值。
3. 边界处理:气泡别跑到屏幕外面
如果控件在窗体右侧、气泡又显示在控件右边,可能超出屏幕。
代码里做了判断:
If lX + Forms!frm批注气泡.WindowWidth > ScreenWidthInTwips() Then' 切换到控件左侧lX = ctl.Left - Forms!frm批注气泡.WindowWidth - 100' ... 重新计算屏幕坐标End If
这样无论控件在哪,气泡都能完整显示。
4. 关闭主窗体时别忘关气泡
主窗体的 Form_Close 事件里加了关闭气泡的逻辑。 如果不做这一步,气泡窗体会以"隐藏"状态残留在内存里,下次打开 Access 还在,可能会报错:
Private Sub Form_Close()On Error Resume NextIf CurrentProject.AllForms("frm批注气泡").IsLoaded ThenDoCmd.Close acForm, "frm批注气泡", acSaveNoEnd IfEnd Sub
十、进阶美化(可选)
1)标题栏根据气泡状态动态变色
在 frm批注气泡 里,可以在 ShowTip 方法中给主体加一道顶部彩色分割线,区分不同字段:
' 在 ShowTip 里追加:Select Case sTitleCase "客户名称"Me.Section(acDetail).BackColor = RGB(255, 255, 204) ' 浅黄Case "邮箱"Me.Section(acDetail).BackColor = RGB(230, 255, 230) ' 浅绿Case ElseMe.Section(acDetail).BackColor = RGB(255, 255, 204)End Select
2)给气泡加自动消失计时
鼠标不移开、气泡就一直挂着,某些场景下你可能希望它几秒后自动消失。 在 frm批注气泡 里加一个 Form_Timer:
Private Sub Form_Timer()Me.TimerInterval = 0Me.Visible = FalseEnd Sub
然后在 ShowTip 里启动计时器:
Me.TimerInterval = 5000 ' 5 秒后自动隐藏3)用 API 做圆角矩形
如果希望气泡有圆角效果,可以用 CreateRoundRectRgn + SetWindowRgn API 把气泡窗体切成圆角。 不过这一步对初学者来说不是必须的,浅黄配色 + 无边框已经足够接近 Excel 批注了。
十一、完整制作过程回顾
t_客户 表 | ||
frm客户信息 主窗体,拖入 5 个字段 | ||
frm批注气泡,设置无边框、浅黄背景、放标题和内容标签 | ||
frm批注气泡 里粘贴 ShowTip / HideTip 代码 | ||
modTooltip,粘贴坐标辅助函数 | ||
frm客户信息 里粘贴全部 VBA 代码 | ||
总共约 15 分钟,就能把 Access 录入窗体的提示体验提升到接近 Excel 批注的水平。
十二、总结
Excel 的批注气泡确实好用,但这不代表 Access 做不出来。
这套方案的核心就 3 层:
- 一个独立的小弹出窗
,无边框、浅黄背景、只放文字 MouseMove事件负责在鼠标移到控件时打开气泡、移到空白区时关闭 - 坐标转换 + 边界处理
保证气泡始终出现在正确的位置
做完之后,录入体验会有很明显的变化:
用户不再面对一堆"裸"输入框 每个字段的说明"跟着鼠标走" 和 Excel 批注几乎一样的直观感
如果你的 Access 系统里有录入窗体给团队用,花十几分钟加上这个功能,用户体验会明显上一个台阶。
测试环境:Access 2016 / 2019 / Microsoft 365,Windows 10 / 11。
如果你的团队正在用 Access,或者计划用 Access 搭建业务系统,我们可以提供从培训到落地的全流程支持:
技术培训
Access VBA 从入门到精通(线上/线下均可) Access + SQL Server 企业级开发实战 Access 系统性能优化与架构设计 AI + Access 融合开发专题培训
定制开发
企业 ERP / CRM / 进销存 / WMS / MES 等系统开发 旧 Access 系统升级、性能优化与架构重构 AI 能力集成到现有 Access 业务系统
技术支持
代码审查与重构建议 疑难问题远程诊断 一对一技术辅导
无论是想让团队快速上手 Access 开发,还是需要把现有系统接上 AI,都可以直接联系我们聊聊方案。
联系方式
公众号后台留言 邮箱:will.miao@edonsoft.com 微信:edonsoft 公司网站:www.edonsoft.com

夜雨聆风