系统托盘子系统(CategoryTray)
SDL 提供了系统托盘(Windows 中更准确的称呼是「通知区域」)操作接口,在支持该功能的平台上(Windows/macOS/Linux 主流桌面环境),SDL 应用可创建托盘图标、添加子菜单、复选框、可点击菜单项,并注册回调函数响应用户的点击操作。
系统托盘功能让应用在后台运行时仍能与用户交互,是桌面应用的常用特性(如后台服务、工具类软件)。
函数
- SDL_CreateTray:创建基础系统托盘实例,返回 SDL_Tray 句柄(失败返回 NULL,需初始化视频子系统)
- SDL_CreateTrayWithProperties:创建带自定义属性的托盘实例(支持图标、提示文本等初始配置)
- SDL_DestroyTray:销毁托盘实例,移除系统托盘中的图标和菜单,释放所有关联资源
- SDL_SetTrayIcon:设置托盘图标(传入 SDL_Surface 或 SDL_Texture 类型的图标资源)
- SDL_SetTrayTooltip:设置托盘图标的悬浮提示文本(鼠标悬停时显示)
- SDL_CreateTrayMenu:创建托盘菜单容器,用于存放菜单项
- SDL_CreateTraySubmenu:创建子菜单(嵌套在主菜单中的二级菜单)
- SDL_InsertTrayEntryAt:在指定位置插入托盘菜单项(支持普通项、复选框、分隔符等)
- SDL_RemoveTrayEntry:移除指定的托盘菜单项
- SDL_SetTrayEntryLabel:设置菜单项的显示文本
- SDL_SetTrayEntryChecked:设置复选框类型菜单项的选中状态
- SDL_GetTrayEntryChecked:获取复选框菜单项的选中状态
- SDL_SetTrayEntryEnabled:设置菜单项是否可用(禁用时灰显,无法点击)
- SDL_GetTrayEntryEnabled:获取菜单项的启用状态
- SDL_GetTrayEntryLabel:获取菜单项的显示文本
- SDL_SetTrayEntryCallback:为菜单项注册点击回调函数
- SDL_ClickTrayEntry:手动触发菜单项的点击事件(模拟用户操作)
- SDL_GetTrayEntries:获取指定菜单下的所有菜单项列表
- SDL_GetTrayEntryParent:获取菜单项所属的父菜单/父托盘
- SDL_GetTrayMenu:获取托盘实例关联的主菜单
- SDL_GetTraySubmenu:获取菜单项关联的子菜单(若有)
- SDL_GetTrayMenuParentEntry:获取子菜单所属的父菜单项
- SDL_GetTrayMenuParentTray:获取菜单所属的父托盘实例
- SDL_UpdateTrays:更新所有托盘实例的显示(修改菜单/图标后需调用)
数据类型
- SDL_Tray:系统托盘实例句柄类型,封装平台相关的托盘资源(如 Windows 的 NOTIFYICONDATA)
- SDL_TrayMenu:托盘菜单容器类型,管理多个菜单项的集合
- SDL_TrayEntry:托盘菜单项类型,代表单个可点击/可选的菜单元素(普通项、复选框、子菜单、分隔符)
- SDL_TrayEntryFlags:菜单项标志类型,标识菜单项类型(普通项、复选框、分隔符、子菜单等)
- SDL_TrayCallback:菜单项通用回调函数类型,响应菜单项的状态变更
- SDL_TrayClickCallback:菜单项点击回调函数类型,用户点击菜单项时触发
结构体
- (无)
枚举
- (无)
宏
- (无)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 补充类型/函数声明(FreeBASIC 绑定可能缺失)
Type SDL_Tray As Any Ptr
Type SDL_TrayMenu As Any Ptr
Type SDL_TrayEntry As Any Ptr
Type SDL_TrayEntryFlags As Uint32
' 菜单项类型标志(示例值,实际 SDL 定义以官方为准)
Const SDL_TRAYENTRY_NORMAL = 0 ' 普通菜单项
Const SDL_TRAYENTRY_CHECKBOX = 1 Shl 0 ' 复选框菜单项
Const SDL_TRAYENTRY_SEPARATOR = 1 Shl 1 ' 分隔符
Const SDL_TRAYENTRY_SUBMENU = 1 Shl 2 ' 子菜单入口
' 回调函数类型定义
Type SDL_TrayClickCallback As Sub( _
ByVal entry As SDL_TrayEntry, _
ByVal userdata As Any Ptr _
)
' 核心函数声明
Declare Function SDL_CreateTray CDecl () As SDL_Tray
Declare Sub SDL_DestroyTray CDecl (ByVal tray As SDL_Tray)
Declare Function SDL_SetTrayIcon CDecl (ByVal tray As SDL_Tray, ByVal icon As SDL_Surface Ptr) As SDL_bool
Declare Function SDL_SetTrayTooltip CDecl (ByVal tray As SDL_Tray, ByVal tooltip As ZString Ptr) As SDL_bool
Declare Function SDL_CreateTrayMenu CDecl () As SDL_TrayMenu
Declare Function SDL_InsertTrayEntryAt CDecl ( _
ByVal menu As SDL_TrayMenu, _
ByVal index As Integer, _
ByVal flags As SDL_TrayEntryFlags, _
ByVal label As ZString Ptr, _
ByVal userdata As Any Ptr _
) As SDL_TrayEntry
Declare Function SDL_CreateTraySubmenu CDecl (ByVal entry As SDL_TrayEntry) As SDL_TrayMenu
Declare Sub SDL_SetTrayEntryCallback CDecl (ByVal entry As SDL_TrayEntry, ByVal callback As SDL_TrayClickCallback)
Declare Sub SDL_SetTrayEntryChecked CDecl (ByVal entry As SDL_TrayEntry, ByVal checked As SDL_bool)
Declare Function SDL_GetTrayEntryChecked CDecl (ByVal entry As SDL_TrayEntry) As SDL_bool
Declare Sub SDL_SetTrayEntryEnabled CDecl (ByVal entry As SDL_TrayEntry, ByVal enabled As SDL_bool)
Declare Function SDL_GetTrayMenu CDecl (ByVal tray As SDL_Tray) As SDL_TrayMenu
Declare Sub SDL_SetTrayMenu CDecl (ByVal tray As SDL_Tray, ByVal menu As SDL_TrayMenu)
Declare Sub SDL_UpdateTrays CDecl ()
' 全局托盘实例
Dim Shared As SDL_Tray g_tray = NULL
' 全局退出标志
Dim Shared As SDL_bool g_quit = SDL_FALSE
' 菜单项点击回调:显示主窗口
Sub ShowWindowCallback(ByVal entry As SDL_TrayEntry, ByVal userdata As Any Ptr)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "【托盘回调】用户点击了「显示窗口」")
' 模拟显示主窗口逻辑
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "→ 显示应用主窗口")
End Sub
' 菜单项点击回调:切换静音状态
Sub ToggleMuteCallback(ByVal entry As SDL_TrayEntry, ByVal userdata As Any Ptr)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "【托盘回调】用户点击了「静音模式」")
' 切换复选框状态
Dim As SDL_bool checked = SDL_GetTrayEntryChecked(entry)
SDL_SetTrayEntryChecked(entry, Not checked)
' 刷新托盘显示
SDL_UpdateTrays()
' 模拟静音逻辑
If (Not checked) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "→ 开启静音模式")
Else
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "→ 关闭静音模式")
End If
End Sub
' 菜单项点击回调:退出应用
Sub ExitAppCallback(ByVal entry As SDL_TrayEntry, ByVal userdata As Any Ptr)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "【托盘回调】用户点击了「退出」")
g_quit = SDL_TRUE
End Sub
' 创建系统托盘及菜单
Function CreateSystemTray() As SDL_bool
' 1. 创建托盘实例
g_tray = SDL_CreateTray()
If (g_tray = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建托盘失败:%s", SDL_GetError())
Return SDL_FALSE
End If
' 2. 设置托盘提示文本(鼠标悬浮显示)
If (SDL_SetTrayTooltip(g_tray, StrPtr("SDL 托盘示例")) = SDL_FALSE) Then
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "设置托盘提示失败:%s", SDL_GetError())
End If
' 3. 创建托盘主菜单
Dim As SDL_TrayMenu Ptr mainMenu = SDL_CreateTrayMenu()
If (mainMenu = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建主菜单失败:%s", SDL_GetError())
SDL_DestroyTray(g_tray)
Return SDL_FALSE
End If
' 4. 添加菜单项:显示窗口(普通项)
Dim As SDL_TrayEntry Ptr showWindowEntry = SDL_InsertTrayEntryAt( _
mainMenu, _
0, _
SDL_TRAYENTRY_NORMAL, _
StrPtr("显示窗口"), _
NULL _
)
If (showWindowEntry <> NULL) Then
SDL_SetTrayEntryCallback(showWindowEntry, @ShowWindowCallback)
End If
' 5. 添加菜单项:分隔符
SDL_InsertTrayEntryAt( _
mainMenu, _
1, _
SDL_TRAYENTRY_SEPARATOR, _
NULL, _
NULL _
)
' 6. 添加菜单项:静音模式(复选框)
Dim As SDL_TrayEntry Ptr muteEntry = SDL_InsertTrayEntryAt( _
mainMenu, _
2, _
SDL_TRAYENTRY_CHECKBOX, _
StrPtr("静音模式"), _
NULL _
)
If (muteEntry <> NULL) Then
SDL_SetTrayEntryChecked(muteEntry, SDL_FALSE) ' 默认未选中
SDL_SetTrayEntryCallback(muteEntry, @ToggleMuteCallback)
End If
' 7. 添加菜单项:退出(普通项)
Dim As SDL_TrayEntry Ptr exitEntry = SDL_InsertTrayEntryAt( _
mainMenu, _
3, _
SDL_TRAYENTRY_NORMAL, _
StrPtr("退出"), _
NULL _
)
If (exitEntry <> NULL) Then
SDL_SetTrayEntryCallback(exitEntry, @ExitAppCallback)
End If
' 8. 将菜单关联到托盘
SDL_SetTrayMenu(g_tray, mainMenu)
' 9. 更新托盘显示
SDL_UpdateTrays()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "系统托盘创建成功")
Return SDL_TRUE
End Function
' 主程序
Sub Main()
' 初始化 SDL(必须初始化视频子系统)
If (SDL_Init(SDL_INIT_VIDEO) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 创建系统托盘
If (CreateSystemTray() = SDL_FALSE) Then
SDL_Quit()
Exit Sub
End If
' 主事件循环(保持应用运行,等待用户操作)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "应用后台运行中,点击托盘「退出」结束程序...")
While (g_quit = SDL_FALSE)
Dim As SDL_Event evt
' 处理 SDL 事件(托盘点击事件需通过事件循环分发)
While (SDL_PollEvent(@evt))
If (evt.type = SDL_QUIT) Then
g_quit = SDL_TRUE
End If
Wend
SDL_Delay(100) ' 降低CPU占用
Wend
' 销毁托盘实例
If (g_tray <> NULL) Then
SDL_DestroyTray(g_tray)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "系统托盘已销毁")
End If
' 清理 SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End Sub
' 运行主程序
Main()
核心知识点补充
-
关键前置条件:
- 必须初始化 SDL 视频子系统(
SDL_Init(SDL_INIT_VIDEO)),托盘依赖窗口系统资源; - 托盘创建后需调用
SDL_UpdateTrays()才能生效(修改菜单/图标后也需调用); - 应用需保持事件循环运行,否则无法响应托盘点击事件。
- 必须初始化 SDL 视频子系统(
-
菜单项类型说明: 标志常量 菜单项类型 用途 SDL_TRAYENTRY_NORMAL 普通菜单项 点击触发回调(如「显示窗口」「退出」) SDL_TRAYENTRY_CHECKBOX 复选框项 可选中/取消选中(如「静音模式」) SDL_TRAYENTRY_SEPARATOR 分隔符 视觉分隔不同功能的菜单项 SDL_TRAYENTRY_SUBMENU 子菜单入口 点击展开二级菜单 -
平台兼容性: 平台 支持情况 特殊说明 Windows 完全支持(所有功能) 托盘图标显示在通知区域,支持所有菜单项类型 macOS 完全支持(所有功能) 托盘图标显示在菜单栏右侧,样式适配macOS Linux 大部分支持(依赖桌面环境) GNOME/KDE 支持良好,轻量桌面可能部分功能失效 移动平台 不支持 无系统托盘概念 -
资源管理注意:
- 必须调用
SDL_DestroyTray()销毁托盘实例,否则会导致资源泄漏(尤其 Windows 平台); - 菜单项的用户数据(userdata)需自行管理生命周期,避免悬空指针;
- 托盘图标建议使用 16x16/32x32 尺寸的 PNG/BMP,保证不同 DPI 下显示清晰。
- 必须调用
总结
-
核心优势:
- 跨平台统一的系统托盘接口,无需适配 Windows 的 Shell_NotifyIcon、macOS 的 NSStatusItem 等原生 API;
- 支持丰富的菜单类型(普通项、复选框、子菜单、分隔符),满足桌面应用的交互需求;
- 回调机制清晰,可精准响应用户对菜单项的点击操作;
- 与 SDL 事件循环无缝集成,保证应用后台运行时的响应性。
-
使用建议:
- 托盘创建后需关联菜单才能显示右键菜单,空托盘仅显示图标无交互;
- 复选框菜单项需手动调用
SDL_SetTrayEntryChecked()切换状态,并调用SDL_UpdateTrays()刷新; - 退出应用前必须销毁托盘实例,避免残留托盘图标(Windows 平台易出现此问题);
- 托盘图标建议提供多分辨率版本,适配不同系统的显示缩放比例。
-
关键点回顾:
SDL_Tray是托盘核心句柄,所有托盘操作均基于此;- 修改托盘菜单/图标后必须调用
SDL_UpdateTrays()才能生效; - 菜单项点击回调需配合 SDL 事件循环才能触发,应用需保持事件循环运行;
- 不同平台的托盘样式有差异,但接口行为一致,无需额外适配。
评论一下?