全局热键、托盘功能和随机启动
作者:Jason Lee
日期:2010-04-24
平台:Qt SDKv2010.02.1 + Windows Xp
声明:文章作者仅在Intel软件网络和CSDN博客发表本文,如有转载,请注明出处
[1]全局热键
Qt事件模型提供了这么一个功能:在一个QObject实例遇到属于它的事件之前,可以通过设置另外一个QObject实例来监视(过滤)这些事件。我们称监视事件的对象为事件过滤器,即eventFilter。
通过QObject的eventFilter和installEventFilter两个函数可以实现事件过滤的功能。前者的原型如下:
bool QObject::eventFilter(QObject *watched, QEvent *event)
该函数是一个虚函数,所以应该由子类(即用户自定义的继承于 QObject 类)来重新实现。实现了该虚函数的对象可以作为一个事件过滤器被安装到指定对象上,目标对象通过使用 installEventFilter 来安装事件过滤器:
void QObject::installEventFilter(QObject *filterObj)
上述是一段引言,现在回到全局热键的问题。
要在 windows 下实现捕获全局热键的功能,自然免不了调用 windows API ,我们首先通过调用 API 向系统注册全局热键,然后通过事件过滤器来处理热键消息。
注册全局热键的 API 如下:
BOOL RegisterHotKey( __in HWND hWnd, __in int id, __in UINT fsModifiers, __in UINT vk );
第一个参数是用来接收热键消息的窗体句柄,我们将其设置为 Qt 中窗口 ID ;第二个参数是要设置的热键标识;第三个参数代表组合键;第四个参数是热键的虚拟键值。更详细的信息可参阅 MSDN 。
当向系统注册热键成功后,就需要事件过滤器的功能了。由于这里的事件是来自于 windows ,所以相应的事件过滤器就有点不同——需要使用 winEventFilter :
bool QCoreApplication::winEventFilter(MSG *msg long *result)
该成员函数也是一个虚函数,通过该过滤器可以处理 Qt 外部的消息。并且由于该函数是 QCoreApplication 的成员,要重写该虚函数就要求我们自定义一个 QCoreApplication 的子类:
#ifndef QTAPP_H #define QTAPP_H #include <QApplication> #include <windows.h> class MyApp : public QApplication{ Q_OBJECT public: MyApp(int &argc, char **argv); ~MyApp(); virtual bool winEventFilter(MSG *msg, long *result); signals: void getF10HotKey(); void getF11HotKey(); }; #endif // QTAPP_H
最后我们在 winEventFilter 中处理热键消息,并通过自定义的热键信号来连接目标槽。在本实例中,通过热键 F10 和 F11 来切换一个 checkBox 的选中状态。并且因为是全局热键,所以即便程序失去焦点,也可以响应到热键消息。
[2] 托盘功能
Qt 中的托盘功能是通过 QSystemTrayIcon 类实现的。同时,该类还可以通过 setContextMenu 成员函数与 QMenu 类结合来产生一个上下文菜单。以下是一段具体代码:
trayIcon = new QSystemTrayIcon(this); trayIcon->setIcon(QIcon(":/images/star.png")); trayMenu = new QMenu(this); showAct = new QAction(tr("Show"), this); hideAct = new QAction(tr("Hide"), this); exitAct = new QAction(tr("Exit"), this); trayMenu->addAction(showAct); trayMenu->addAction(hideAct); trayMenu->addSeparator(); trayMenu->addAction(exitAct); connect(showAct, SIGNAL(triggered()), this, SLOT(slotShowAct())); connect(hideAct, SIGNAL(triggered()), this, SLOT(slotHideAct())); connect(exitAct, SIGNAL(triggered()), this, SLOT(close())); trayIcon->setContextMenu(trayMenu); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayActived(QSystemTrayIcon::ActivationReason))); trayIcon->show();
系统托盘通过设置上下文菜单后,可以添加各种动作,从而产生一些指定行为。除此之外,如果我们要更生动地实现系统托盘图标的显隐,比如点击关闭或者最小化按钮后将程序隐藏到系统托盘中,而非实际关闭应用程序,可以通过结合 closeEvent 或者结合 changeEvent 来判断 windowState 来重写当用户点击关闭或者最小化按钮时的处理代码。
另外,托盘图标可以做到的更生动的事是当有需要的时候可以弹出一个气泡消息提示用户,比如告知用户某项下载任务已经完成。该功能则可以通过 QSystemTrayIcon 的成员函数 showMessage 来实现。
托盘功能的效果图如下:
[3] 随机启动
在 Windows 下实现随机启动的功能,一般都是通过操作注册表来实现的。我们可以考虑的 API 函数有:
LONG WINAPI RegCreateKeyEx( __in HKEY hKey, __in LPCTSTR lpSubKey, __reserved DWORD Reserved, __in_opt LPTSTR lpClass, __in DWORD dwOptions, __in REGSAM samDesired, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __out PHKEY phkResult, __out_opt LPDWORD lpdwDisposition );
可以通过结合第一个参数 hKey 和第二个参数 lpSubKey 来指定要在注册表哪个位置上创建一个新项,并且由倒数第二个参数 phkResult 来指向获得的项。其它的具体参数含义详见 MSDN 。(项的解释,来源于中文版的 windows 操作系统中的注册表)
LONG WINAPI RegOpenKeyEx( __in HKEY hKey, __in_opt LPCTSTR lpSubKey, __reserved DWORD ulOptions, __in REGSAM samDesired, __out PHKEY phkResult );
显然,同样是通过 hKey 和 lpSubKey 来指定路径,然后通过 phkResult 来指向获得的项。
LONG WINAPI RegSetValueEx( __in HKEY hKey, __in_opt LPCTSTR lpValueName, __reserved DWORD Reserved, __in DWORD dwType, __in_opt const BYTE *lpData, __in DWORD cbData );
当我们打开一个项时,我们就获得了该项的句柄。接着对该项进行操作时,比如修改某个值的内容,就需要该项的句柄作为第一个参数 hKey 。第二个参数是值名,如果项中不存在该值名则以改名添加一个新值到该项中。结合图说明可以更加具体:
截止到 Run 是随机启动项在注册表中所处的位置。
以上就是项中的值,有多种类型。我们可以通过往 Run 项添加新值来新增一个自启动项目。
LONG WINAPI RegCloseKey( __in HKEY hKey );
操作结束后,需要记得关闭该项。
[ 后记 ] 我本来的预想示例程序是通过热键来切换 checkBox 的选中状态,进而控制程序是否随机启动,并且实现下系统托盘的简单功能。但写到这里才发现已经凌晨,明天又有事情,所以目前只实现了全局热键和系统托盘的功能,而对于随机启动,只放了两个空函数在代码中还没来得及写,只是对相关 API 进行了一番粗略概览。
希望大家不吝赐教。晚安 & 早安!
本实例源码请见:http://download.csdn.net/source/2281281