导读

一款流行的软件,往往会在功能渐趋完善的时候,通过改善交互界面来提高用户体验。毕竟,就算再牛逼的产品,躲藏在糟糕的用户界面之后总会让用户心生不满。界面设计需综合考虑审美学、心理学、设计学等多因素,是一份精细活。这篇博文仍然以Qt的使用为主旨,探讨一下在Qt中如何进行系统托盘的个性化定制。

介绍

首先我们看看几款知名软件的系统托盘设计:

上图是金山卫士的系统托盘菜单设计。我们稍作分析:整个托盘菜单窗口是个半透明的设计,窗口边框进行了圆角处理。底部的菜单项包含三个Button,倒数第二、三个菜单项的右部还加上了一个自定义的单选按钮。顶部菜单项则包含一个评级组件;其他菜单项则没有什么特别,加上对应的图标即可完成设计。但是可能由于整个背景色的缘故,导致整体效果看起来灰蒙蒙的,不太亮堂。

上图是360安全卫士的托盘菜单。顶部和底部的两个菜单项都将背景色设置成了360安全卫士的主题色,加上了两个标签和按钮。其他菜单项保持不变。另外,菜单的背景色也被设置成了白色。整个菜单的设计较为简洁、清爽。虽然并不喜欢用360安全卫士,但是并不妨碍我对其产品外观设计的赞赏。

原型设计

既然有了上述两款产品的参考,我们也可以试着设计下自己的系统托盘。首先我们需要一个原型设计工具,将草图绘制好我们才能用代码将最终结果显示出来。这里推荐一个原型设计工具:Balsqmiq mockup。这款工具使用简单,其提供的原型组件非常丰富,使用会觉得非常方便。

根据初步设想,我设计了如下的一个原型草图:

在布局方面基本上综合了金山卫士和360安全卫士的设计特点。顶部菜单项部署两个Label, 一个用来显示应用程序的窗口标题或产品名称,另一个显示为go to visit,可以响应鼠标点击事件。底部菜单项和金山卫士一样,设置了三个按钮:Update, about, exit,使用水平均匀布局。其他的菜单项则和普通菜单项没有区别。 基本上,一个自定义的托盘菜单已经跃然而出。

代码实现

根据上述的原型设计,我们要做的准备工作显然就是准备好图片。对于没有美工技能的程序员来说,寻找界面图片素材显然是一大难题。做不出图片显然只好去网上搜索了。本人在网上下载了一堆的图片压缩包,有一个值得推荐:异次元图标。另外还有一个图片搜索网站也值得推荐。在这里我准备的图片如下:

每个图片都取了一个别名,这样在代码中我们直接使用图片别名,从而消除与图片具体名称的藕合性。资源准备好之后我们需要开始编码了。参考本人曾经写过的一篇博文(使用Qt创建系统托盘),可以实现一个默认主题的系统托盘菜单。但是这里我们要实现自定义托盘菜单,我们从QSystemTray派生一个子类,并定义好相关的类成员变量:

QMenu* m_trayMenu;

QWidget* m_topWidget;
QWidgetAction* m_topWidgetAction;
QLabel* m_topLabel;
QLabel* m_homeBtn; QWidget* m_bottomWidget;
QWidgetAction* m_bottomWidgetAction;
QPushButton* m_updateBtn;
QPushButton* m_aboutBtn;
QPushButton* m_exitBtn; QAction* m_runOnSystemBoot;
QAction* m_helpOnline;
QAction* m_homePage;
QAction* m_notification;
QAction* m_settings;

  显然,我们注意到一个平时没有接触到的:QWidgetAction。这个类自Qt 4.2引入,继承自QAction。根据类名也可以推测出其含义:使用QWidget来充当Menu的Action。于是,我们似乎明白了自定义菜单的精髓:用Widget来做Action。这里我们主要定义顶部菜单项和底部菜单项。因此我们定义了两个QWidgetAction。另外,我们还有一个疑问就是:布局好的Widget如何"伪装"成Action插入到菜单项中去呢?我们可以使用QWidgetAction的setDefaultWidget()方法来完成这项工作。后面的代码将会有说明。

此外,我们还注意到:360安全卫士的底部菜单项和顶部菜单项的背景色都是绿色的这又该如何实现呢?一种可行的方法是,安装一个事件过滤器(Event Filter)。当过滤到绘制事件并且绘制的组件是顶部菜单项和底部菜单项时,我们改变绘制方式。代码如下:

bool SystemTray::eventFilter(QObject *obj, QEvent *event)
{
if (obj == m_topWidget && event->type() == QEvent::Paint)
{
QPainter painter(m_topWidget);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(42, 120, 192));
painter.drawRect(m_topWidget->rect());
}
return QSystemTrayIcon::eventFilter(obj, event);
}

  在完成了我们自己的绘制工作之后,还得再调用父类的事件过滤器,以免漏掉其他过滤工作。eventFilter()是一个protected方法,我们要在头文件中进行重写。

接下来要做的工作就是完成顶部和底部菜单项的绘制工作。先看看顶部菜单项如何绘制:

void SystemTray::createTopWidget()
{
m_topWidget = new QWidget();
m_topWidgetAction = new QWidgetAction(m_trayMenu);
m_topLabel = new QLabel(QStringLiteral("HUST Information Security Lab"));
m_topLabel->setObjectName(QStringLiteral("WhiteLabel"));
m_homeBtn = new QLabel(QStringLiteral("Visit"));
m_homeBtn->setCursor(Qt::PointingHandCursor);
m_homeBtn->setObjectName(QStringLiteral("WhiteLabel")); QVBoxLayout* m_topLayout = new QVBoxLayout();
m_topLayout->addWidget(m_topLabel, 0, Qt::AlignLeft|Qt::AlignVCenter);
m_topLayout->addWidget(m_homeBtn, 0, Qt::AlignRight|Qt::AlignVCenter); m_topLayout->setSpacing(5);
m_topLayout->setContentsMargins(5, 5, 5, 5); m_topWidget->setLayout(m_topLayout);
m_topWidget->installEventFilter(this);
m_topWidgetAction->setDefaultWidget(m_topWidget);
}

  我们声明了两个Label标签,作用在上文已说明。然后用垂直布局管理器将两个标签分左右放置。注意语句:m_topWidget->installEventFilter(this)。这条语句完成了过滤器的安装。指针this表明窗口事件将先发往当前类的eventFilter()方法进行处理,如果不处理再发往其他类的过滤器进行处理。底部菜单项的初始化大致类似:

void SystemTray::createBottomWidget()
{
m_bottomWidget = new QWidget();
m_bottomWidgetAction = new QWidgetAction(m_trayMenu); m_updateBtn = new QPushButton(QIcon(":/menu/update"), QStringLiteral("Update"));
m_updateBtn->setObjectName(QStringLiteral("TrayButton"));
m_updateBtn->setFixedSize(60, 25); m_aboutBtn = new QPushButton(QIcon(":/menu/about"), QStringLiteral("About"));
m_aboutBtn->setObjectName(QStringLiteral("TrayButton"));
m_aboutBtn->setFixedSize(60, 25); m_exitBtn = new QPushButton(QIcon(":/menu/quit"), QStringLiteral("Exit"));
m_exitBtn->setObjectName(QStringLiteral("TrayButton"));
m_exitBtn->setFixedSize(60, 25); QHBoxLayout* m_bottomLayout = new QHBoxLayout();
m_bottomLayout->addWidget(m_updateBtn, 0, Qt::AlignCenter);
m_bottomLayout->addWidget(m_aboutBtn, 0, Qt::AlignCenter);
m_bottomLayout->addWidget(m_exitBtn, 0, Qt::AlignCenter); m_bottomLayout->setSpacing(5);
m_bottomLayout->setContentsMargins(5,5,5,5); m_bottomWidget->setLayout(m_bottomLayout);
m_bottomWidgetAction->setDefaultWidget(m_bottomWidget);
}

  分别对三个按钮设置了大小和图标。具体的外观样式则使用了QSS来进行控制,因此我们还为每个按钮设置了一个Object Name。这个Object Name在QSS中充当ID选择器,便于样式控制。那么样式文件该如何编写呢?具体参看如下所示:

QMenu{
background:white;
border:1px solid lightgray; # 边框为灰色
} QMenu::item{
padding:0px 20px 0px 20px;
margin-left: 5px;
height:25px;
} QMenu::item:selected:enabled{
background: lightgray; # 菜单项选中时背景色设置为浅灰色
color: white; # 文本颜色设置为白色,否则看不清文本内容了
} QMenu::separator{
height:1px;
background: lightgray; # 菜单分割线也设置为浅灰色
margin:2px 0px 2px 0px;
} QMenu::item:selected:!enabled{
background:transparent;
} QPushButton#TrayButton {
border: none; # 无边框按钮
background: transparent; # 按钮背景设置为透明,这样不会受到默认主题颜色干扰
} QPushButton#TrayButton:hover {
background: rgb(233, 237, 252); # 鼠标悬停时,按钮背景色设为淡色
color: rgb(42, 120, 192); # 鼠标悬停时,文本颜色不变
}

  基本上,使用上面的样式设置就可完成基本样式设置。其他代码就不再详细叙述。到此,我们的托盘菜单就完成了个性化定制工作。

效果图

根据上述代码,我们实现的最终效果图如下:

前面也说过:界面设计是一门学问,综合了设计学、心理学、审美学等多学科。要设计出让人耳目一新的产品界面,需要设计师具备相当的设计功力。但不管最终设计的怎么样,我们已经知道了,如何实现具备个人特点的托盘菜单!

参考

用Qt写软件系列四:定制个性化系统托盘菜单的更多相关文章

  1. 用Qt写软件系列三:一个简单的系统工具(上)

    导言 继上篇<用Qt写软件系列二:QIECookieViewer>之后,有一段时间没有更新博客了.这次要写的是一个简单的系统工具,需求来自一个内部项目.功能其实很简单,就是查看当前当前系统 ...

  2. 用Qt写软件系列五:一个安全防护软件的制作(1)

    引言 又有许久没有更新了.Qt,我心爱的Qt,为了找工作不得不抛弃一段时间,业余时间来学一学了.本来计划要写一系列关于Qt组件美化的博文,但是写了几篇之后就没坚持下去了.技术上倒是问题不大,主要是时间 ...

  3. 用Qt写软件系列三:一个简单的系统工具之界面美化

    前言 在上一篇中,我们基本上完成了主要功能的实现,剩下的一些导出.进程子模块信息等功能,留到后面再来慢慢实现.这一篇来讲述如何对主界面进行个性化的定制.Qt库提供的只是最基本的组件功能,使用这些组件开 ...

  4. 用Qt写软件系列二:QCookieViewer(浏览器Cookie查看器)

    预备 继上篇<浏览器缓存查看器QCacheViewer>之后,本篇开始QCookieViewer的编写.Cookie技术作为网站收集用户隐私信息.分析用户偏好的一种手段,广泛应用于各大网站 ...

  5. 用Qt写软件系列一:QCacheViewer(浏览器缓存查看器)

    介绍 Cache技术广泛应用于计算机行业的软硬件领域.该技术既是人们对新技术探讨的结果,也是对当前软硬件计算能力的一种妥协.在浏览器中使用cache技术,可以大幅度提高web页面的响应速度,降低数据传 ...

  6. 用Qt写软件系列五:一个安全防护软件的制作(3)

    引言 上一篇中讲述了工具箱的添加.通过一个水平布局管理器,我们将一系列的工具按钮组合到了一起,完成了工具箱的编写.本文在前面的基础上实现窗体分割效果.堆栈式窗口以及Tab选项卡. 窗体分割 窗体分割是 ...

  7. 用Qt写软件系列五:一个安全防护软件的制作(2)

    引言 在上一篇中讲述了主窗体的创建和设计.主窗体的无边框效果.阴影效果.拖动事件处理.窗体美化等工作在前面的博客中早就涉及,因此上篇博文中并未花费过多笔墨.这一篇继续讲述工具箱(Tool Button ...

  8. 如何定制Windows系统右键菜单

    今天心血来潮把几个自己常用的工具定制到了系统的右键菜单.包括notepad++,7zip,还有复制文件全路径和文件夹路径.下面简单介绍一下步骤. 1. Windows系统右键菜单对应的注册表位置 Wi ...

  9. linux kernel系列四:嵌入式系统中的文件系统以及MTD

    本节介绍File System和MTD技术 一 FS 熟知的FS有ext2,3,4.但是这些都是针对磁盘设备的.而ES中一般的存储设备为Flash,由于Flash的特殊性: Flash存储按照Bloc ...

随机推荐

  1. 2014 Hangjs 见闻流水账第一天

    前言 6月21日~6月22日, 第一次跑远门去参加一个大会(广州 -> 杭州),本来打算,在火车的回来的路上,把这两天的东西记录一下,不过,火车上的环境实在恶劣,同时也高估了自己的专注力,所以, ...

  2. 怎么在阿里云服务器部署多个tomcat

    部署前准备: 1.到阿里云官网购买一台服务器 2.给阿里云服务器挂盘,阿里云有教程这里不讲解,自己看. Linux 系统挂载数据盘 视频:Linux服务器挂载数据盘 3.下载tomcat  http: ...

  3. ip校验方法:判断ip是否位于指定的范围内

    import java.util.ArrayList;import java.util.List;import java.util.regex.Matcher;import java.util.reg ...

  4. ECSHOP始终显示全部分类方法

    商品分类树需要始终显示所有类别,默认的Ecshop的显示方式为在当前产品页面只显示当前的产品所在的同级及下级分类,这就导致当点开某个产品或者子分 类的时候全局的分类树就不见了. 其实修改的方法很简单. ...

  5. Fixed error when submitting assignments in Machine Learning on Coursera

    Environment: OS: OSX 10.8.5 Matlab: R2013a(8.1.0.604) 64bit   How to fix: In file submit.m, line 129 ...

  6. 通过transform属性改变图片的位置大小等信息

    对UIImageView的位置大小方向的改变可以通过改变其transform属性值实现. 位置改变: var transform = CGAffineTransformMakeTranslation( ...

  7. OpenSSL Command-Line HOWTO

    OpenSSL Command-Line HOWTO The openssl application that ships with the OpenSSL libraries can perform ...

  8. 关于Task类处理多线程简单示例

    1.定义一个线程 var task1 = Task.Factory.StartNew(() => DoSomeWork()):方法如下:          private static obje ...

  9. Linux RPM 命令参数使用详解

    rpm 执行安装包二进制包(Binary)以及源代码包(Source)两种.二进制包可以直接安装在计算机中,而源代码包将会由 RPM自动编译.安装.源代码包经常以src.rpm作为后缀名. 常用命令组 ...

  10. android dalvik heap 浅析

    android 系统中可以在/system/build.prop中配置dalvik堆的有关设定.具体设定由如下三个属性来控制 -dalvik.vm.heapstartsize  堆分配的初始大小,调整 ...