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

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变量;然后将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方便地转换。

你可能感兴趣的:(软件层系统功能模块——托盘管理)