软件层系统功能模块——托盘管理

books项目是一个托盘程序,程序登录后直接进入Windows系统托盘。Qt程序是跨平台程序,由于有些平台没有系统托盘概念,因此需要对Windows系统托盘进行特殊处理。

Windows 系统托盘

在Qt中新增QSystemTrayIcon变量,系统将自动增加系统托盘功能。在dialog.cpp中增加代码,具体如下:

Dialog::Dialog(QWidget *parent) :
    QDialog(parent), 
    ui(new Ui::Dialog)
{
      // 1.托盘功能
      // 1.1初始化图标
      iconCorrect = QIcon(":/images/correct.png");
      iconError = QIcon(":/images/error.png");
      // 1.2初始化托盘图标
      trayIcon = new QSystemTrayIcon(this);
      trayIcon->setToolTip("单击显示系统信息\n右击显示菜单功能");
      setBothIcons(0);
      trayIcon->setVisible(true);
}
void Dialog::setBothIcons(int index)      // 设置程序图标(窗口图标 + 托盘图标)
{
      // index = 0;无错误
      // index = 1;有错误,显示错误图标
      // 设置对话框窗口图标
      (index == 0) ? setWindowIcon(iconCorrect) : setWindowIcon(iconError);
      // 设置托盘图标
      (index == 0) ? trayIcon->setIcon(iconCorrect) : trayIcon->setIcon(iconError);
}

首先上述代码为托盘准备了两个图标:一个是程序正确运行时的图标;一个是程序出错时的图标。其次,创建Windows托盘图标变量:利用setToolTip()函数设置提示信息,当用户鼠标移动到系统托盘图标上时,提示信息自动显示;利用setBothIcons(int)函数实现图标设置,该函数根据传入的参数值确定设置正确图标还是错误图标。函数内部不仅为托盘设置了相应图标,还为对话框设置了窗口图标,同样分为正确和错误两种图标。最后,使用setVisible(bool)函数将系统托盘图标显示在右下角。

程序使用的相关变量及需要引入的相关头文件均在dialog.h中定义,可查阅initxuan/books中的相关代码。

接下来运行编码完成的程序,单击程序界面右上角"关闭"按钮,程序将直接退出,而未最小化到系统托盘中。这是因为单击"关闭"按钮后程序进入系统托盘,但紧接着执行Qt关闭对话框的默认动作,退出了程序。这时我们需要对关闭对话框按钮的事件进行处理,以实现最小化或关闭程序时把窗口隐藏,系统托盘图标仍旧显示,然后通过系统托盘图标上的菜单来操控整个程序。

事件劫持

在Qt中处理事件,只需要在头文件标识"slots:"(槽)的位置上加上事件函数名,然后在cpp文件中把处理事件的代码实现即可。处理books项目的关闭事件函数名为closeEvent(),在这个函数中"劫持"关闭事件,然后做自己的处理。这个函数是Qt框架自带的函数,专门用于"劫持"关闭事件。相关代码如下:

void Dialog::closeEvent(QCloseEvent *event)    // 隐藏窗口
{
        if(trayIcon->isVisible()){
                if(isLogin) hide();          // 登录用户直接隐藏窗口进入托盘
                else{
                          QMessageBox::information(this, tr("远程传输与控制系统"),
                          tr("请登录。\n\n退出程序:请右击系统托盘图标,选择退出程序"));
                }
                event->ignore();
        }
}

用户单击程序界面右上角的"关闭"按钮触发closeEvent()函数,该函数先判断系统托盘是否可见,即是否处于激活状态(托盘可见是指托盘功能处于激活状态)。如果可见,则判断用户是否登录,如果已登录,则使用框架自带的系统函数hide()隐藏窗口,使程序进入系统托盘。hide()函数实现了系统托盘程序的最核心功能。如上文所述,隐藏窗口后只保留系统托盘一个界面,所有操作都是通过托盘图标进行的,这符合系统托盘程序(或伪后台程序)的要求。如果用户未登录,则让用户登录后再进入系统托盘。最后利用event->ignore()函数将程序关闭事件丢弃,否则关闭事件将沿着Qt定义的事件处理流程继续前行,由Qt实现默认管理,窗口仍将在进入托盘后被关闭。

菜单管理

因为系统进入系统托盘后,表现为伪后台程序运行状态。在这种情况下,用户对程序的所有操作都只能通过托盘图标进行,因此要对托盘图标设计菜单,实现用户接口功能。

在Windows下单击和右击托盘图标一般都会出现不同的菜单。延续惯例,在books项目中右击托盘图标后将显示系统的功能菜单,如注销、退出程序等。单击托盘图标后将产生系统消息菜单,即用户当前执行的任务的结果,以气泡式菜单的形式显示给用户。

鼠标右键动作

右击托盘图标时将显示系统的功能菜单,因为Qt Creator没有提供针对托盘菜单编辑的可视化GUI设计界面,所以只能通过编写代码的方式实现托盘功能菜单的设计工作。在Qt中要显示功能菜单需要2个类,它们对应的头文件分别为<QAction><QMenu>。实现菜单的整个过程如下:先定义QAction变量;然后将QAction变量当作参数生成QMenu变量,这个QMenu变量就是菜单项;最后使用定义的托盘图标变量,使用setContextMenu函数将生成的菜单加入托盘图标,图标就会完成识别鼠标右键的功能。

在books项目的dialog.h中,先加入上述2个头文件,然后定义1个QMenu变量和几个QAction变量。定义1个QMenu变量是因为鼠标右键产生的是1个菜单,而几个QAction变量对应菜单中的几项内容。具体代码如下:

void Dialog::setupTrayMenu()
{
        // 要检索INI和设备信息后才处理
        userMsg = new QAction("当前用户:", this);
        deviceMsg = new QAction("当前设备:", this);
        logoutAction = new QAction("注销用户", this);
        quitAction = new QAction("退出程序", this);

        trayIconMenu = new QMenu(this);
        trayIconMenu->addAction(userMsg);
        trayIconMenu->addSeparator();
        trayIconMenu->addAction(deviceMsg);
        trayIconMenu->addSeparator();

        trayIconMenu->addAction(logoutAction);
        connect(logoutAction, SIGNAL(triggered()), this, SLOT(logout())); 

        trayIconMenu->addAction(quitAction);
        connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
        // 非常重要,必须是qApp,不能是this

        trayIcon->setContextMenu(trayIconMenu);
}
void Dialog::logout()
{
        isLogin = false;
        showNormal();    // 框架函数,显示系统登录对话框
}

由上述代码可知,系统一共生成了4个菜单内容,前2项标识了当前用户和当前用户使用的设备,用户登录后或设备被插入计算机时,信息被检索到并添加到菜单冒号后面用于提示用户。获取信息并显示的具体实现细节后面详细介绍。

菜单后2项是注销用户和退出程序。当用户选择这2项时,程序需要做出反应,执行相应功能,此时需要Qt标准事件处理和消息响应函数connect。菜单项发出消息的函数是SIGNAL(triggered()),接收消息的是this状态下定义的SLOT(logout())函数。this表示当前对话框环境,logout()是在this环境下定义的函数,该函数的定义格式(Dialog::logout())表明该函数是在this环境下定义的函数。SIGNAL和SLOT都是Qt的标准宏。connect函数的作用即为当菜单项"logoutAction"被触发(triggered())时自动调用用户自己定义的处理函数logout()。connect函数通过第一个参数标识触发的是哪个菜单项。关于connect函数更多的信息详见后面章节。

注:logout()函数在头文件中声明时与普通函数不同,它必须放在"private slots:"这一项下,向Qt表明这个函数可以受理消息触发。

根据上述内容可知,quitAction菜单项发出SIGNAL(triggered())消息后由SLOT(quit())函数处理。与上一个connect函数不同,第3个参数由this转为qApp。这是因为logout()函数定义在this环境下,而quit()函数定义在qApp环境下。qApp是Qt平台为每个Qt程序定义的应用程序实例,quit()是qApp变量的属性函数,负责退出应用程序。程序正常退出时都会发送closeEvent事件,在"事件劫持"那一节中我们专门截获了单击登录窗口"关闭"按钮的closeEvent事件,并在事件中不允许程序退出。所以让程序退出再发送closeEvent事件则毫无意义。因此此时需要调用Qt程序最底层的qApp->quit()函数退出程序。

addAction函数是将菜单内容加入菜单中,且先加的内容在菜单的上部。addSeparator函数是在菜单中加入横条分隔菜单。

最后要显示编辑好的鼠标右键功能菜单,需要在Dialog::Dialog(QWidget *parent)构造函数中调用setupTrayMenu()函数。

Qt程序退出时最后要调用的是qApp->quit()函数。单击Qt对话框程序右上角的"关闭"按钮,系统将会产生closeEvent事件。在处理closeEvent事件的函数中,如果自主消息处理后不使用event->ignore()强行忽略消息,而是让消息沿事件处理机制由Qt自动处理,那么Qt最后也会调用qApp->quit()函数退出程序。

鼠标左键动作

鼠标左键产生的菜单是消息显示菜单。其分为3步完成相应功能:第1步是响应鼠标左键的消息,Qt对其有明确定义,即

SIGNAL(activated(QSystemTrayIcon::ActivationReason))

用户需要自定义SLOT函数来处理这个消息,即:

SLOT(myIconActivated(QSystemTrayIcon::ActivationReason))

第2步是定义要显示的消息内容,消息内容是字符串类型;第3步是显示消息,使用自定义showMessage函数。具体代码如下:

void Dialog::myIconActivated(QSystemTrayIcon::ActivationReason reason)
{
        switch(reason){
        case QSystemTrayIcon::Trigger:
        case QSystemTrayIcon::DoubleClick:
                showMessage();
                break;
        default:
                break;
        }
}
void Dialog::showMessage()
{
        QString title = "系统状态信息";
        deviceInfo.append("1. aaa");
        deviceInfo.append("2. bbb");
        int duration = 3;
        trayIcon->showMessage(title, 
                              deviceInfo.join("\n"), 
                              QSystemTrayIcon::Information, 
                              duration * 1000);
}

在自定义的myIconActivated函数中,先对产生消息的原因进行判断,如果单击(QSystemTrayIcon::Trigger)或是双击(QSystemTrayIcon::DoubleClick),则执行自定义showMessage()函数显示消息。其他情况不做任何处理。

showMessage()函数中,先定义消息显示界面的标题,然后将消息内容保存到deviceInfo变量中,因为消息内容是字符串类型,且每行消息之间可由"\n"分隔。由于显示的消息通常比较多,考虑到效率,程序使用QStringList类型定义deviceInfo。由于目前程序还未收到任何可以显示的消息,所以使用两组示例性字符串供显示。

最后使用trayIcon->showMessage函数显示消息,函数中先使用deviceInfo.join("\n")将字符串列表转换为字符串,再利用duration定义要显示的消息时长,超过时长菜单将会自动消失。函数接收的时长单位是毫秒,因此需要乘以1000。QSystemTrayIcon::Information参数表示显示消息的界面的左上角显示类似字母"i"的"信息图标",可选的包括QSystemTrayIcon::Warning警告图标和QSystemTrayIcon::Critical严重警告图标等。

如果用户运行当前程序就会发现,每次单击托盘图标时,显示的消息内容就会不断变多。这是因为每次调用消息显示函数showMessage时,deviceInfo都会在原基础上增加显示消息,所以消息才会越来越多。要解决这个问题,可以在showMessage显示消息之前,先使用deviceInfo.clear()函数清空所有旧消息,再加入新消息即可。

QStringList是字符串列表类型,它可以方便地存储多组字符串,并可以与字符串变量QString方便地转换。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,072评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 第一部分概述 第一次建立helloworld程序时,曾看到Qt Creator提供的默认基类只有QMainWind...
    你的社交帐号昵阅读 3,602评论 0 6
  • 今天,雪秀姐离职了。来到人防办两个月不到,先后离职的两位非常好的伙伴,都是很好的女孩子,就算只接触几天,但感性,从...
    米粒Limy阅读 164评论 0 0
  • 夏天已经在急燎燎的路上了 仙女们赶紧换上美美的裙子吧 清凉的同时还要保持仙女风度哦 装不下去了.... 我们是一档...
    艺伙阅读 487评论 0 3