用Qt写软件系列四:定制个性化系统托盘菜单

导读

    一款流行的软件,往往会在功能渐趋完善的时候,通过改善交互界面来提高用户体验。毕竟,就算再牛逼的产品,躲藏在糟糕的用户界面之后总会让用户心生不满。界面设计需综合考虑审美学、心理学、设计学等多因素,是一份精细活。这篇博文仍然以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);    # 鼠标悬停时,文本颜色不变
}

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

效果图

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

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

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端说吧

css3-巧用选择器 “:target”

3686
来自专栏Coco的专栏

妙用 scale 与 transfrom-origin,精准控制动画方向

1934
来自专栏pangguoming

html5调用手机陀螺仪实现方向辨识

我们可以让document监听deviceorientation 来获取相关的数据,里面包括3个值 alpha、beta和gamma。

3101
来自专栏我和未来有约会

用silverlight做动画-相机

用silverlight做动画-相机 适合初学者学习 做一个相机的动画 和做flash动画一样,准备好素材 将素材放入项目中 开始正式制作前为了方便以后重用,...

2844
来自专栏钱塘大数据

【干货】如何提升Excel表格的颜值?

下载了几个歪果仁做的Excel表格,非常的漂亮: ? ? 再看看我们最常见的表格,难看的瞬间爆表 ? 兰色对歪果仁的表格好看的原因进行了归纳,下面我们按歪果仁...

4229
来自专栏老马寒门IT

08-移动端开发教程-移动端适配方案

由于移动端的特殊性,屏幕的尺寸碎片化严重,要想很好的适配不同的尺寸的设备,需要我们前端开发相比PC端要做一些基层的适配方案。

49010
来自专栏编程微刊

用Canvas画一个刮刮乐

2094
来自专栏前端说吧

CSS-用伪类制作小箭头(轮播图的左右切换btn)

4858
来自专栏小筱月

分享一次纯 css 瀑布流 和 js 瀑布流

现在百度图片,360 图片搜索,都是以一种瀑布流的形式展示,那么接下来,分享一波纯 css 瀑布流 和 js 瀑布流

2954
来自专栏Coco的专栏

妙用 scale 与 transfrom-origin,精准控制动画方向

1714

扫码关注云+社区

领取腾讯云代金券