引言

又有许久没有更新了。Qt,我心爱的Qt,为了找工作不得不抛弃一段时间,业余时间来学一学了。本来计划要写一系列关于Qt组件美化的博文,但是写了几篇之后就没坚持下去了。技术上倒是问题不大,主要是时间不够充裕。这段时间写几篇关于界面整体设计的博文,从最基础的界面元素开始,到最后构建一个页面元素丰富的桌面应用程序。Trojan Assessment Platform是一个原型设计项目,只是实现了有限的一部分功能。远远还称不上是一个评估平台。这里仅仅侧重于用Qt做界面的实现。

界面预览

首先还是看看整个程序运行起来是怎么回事:

图一 基本信息页面

图二 实时监控图表

图三 进程快照

用过某些安全防护软件的用户,咋一看会有一种眼熟的感觉。没错,在界面的设计上本人参考了一些成熟软件产品的视觉设计。不过这显然不是关注的重点,用户体验设计上有种说法,遵循统一的界面设计原则,能降低用户的操作成本。这也算是一种业界标准了。

头部Banner

先看看“业界标准”是怎么做的!这里选择了两款具备代表性的软件:360安全卫士和金山卫士:

图 三 360安全卫士的工具箱

图四 金山卫士的工具箱

观察以上两个截图的布局不难发现,界面布局如下:

顶部一个水平布局管理器可以搞定,左端放程序名及Logo,最右端部署按钮。这在Qt里面通过QHBoxLayout很容易做到。下面也用一个水平布局管理器,左端一个工具箱,等距放置,右边放大号的文本及Logo。好吧,开干!!

(1)按钮及文本

关于按钮的自定义绘制在前面的博文中已经有过讲解。但是前面讲的并没有覆盖到如何修改按钮的外观和背景图片。我们的做法是,从QPushButton派生出一个子类,在这个子类中实现图片的切换和状态管理。但是前提是,我们需要准备好按钮不同状态的图片(状态分别为鼠标悬停、按下、正常)。

接下来要做的工作便是派生一个子类:

// CustomPushButton.h
class CustomPushButton : public QPushButton
{
Q_OBJECT public:
explicit CustomPushButton(QWidget *parent = NULL);
~CustomPushButton(){}
enum BtnStatus{NORMAL, PRESSED, HOVER};
void setBtnBackground(const QString& path); private:
CustomPushButton(const CustomPushButton& obj);
CustomPushButton& operator=(const CustomPushButton& obj); protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void enterEvent(QEvent* event);
void leaveEvent(QEvent* event); private:
BtnStatus m_status; // record the status to take different painting action
bool isPressed; // whether the button is pressed.
QString m_imagePath;
};

  我们重写了Button类的一些事件处理函数。因为我们需要对鼠标悬停、进入区域、离开区域进行自行处理,所以我们这里重写了mousePressEvent(), mouseReleaseEvent(), enterEvent(), leaveEvent()这几个方法。在类中我们还定义了几个enum常亮,用来表示按钮的不同状态,在后面将被用到。注意setBtnBackground()函数,用于设置Button的背景图片。再来看看在CPP文件中是怎么实现的:

CustomPushButton::CustomPushButton(QWidget *parent) : QPushButton(parent){}

void CustomPushButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QString pixmapPath;
switch (m_status) // 根据不同状态绘制不同的背景图片
{
case NORMAL:
pixmapPath = m_imagePath;
break;
case HOVER:
pixmapPath = m_imagePath+"_hover";
break;
case PRESSED:
pixmapPath = m_imagePath+"_pressed";
break;
default:
pixmapPath = m_imagePath;
break;
}
// draw the button background
painter.drawPixmap(rect(), QPixmap(pixmapPath)); } void CustomPushButton::mousePressEvent(QMouseEvent *event)
{
// only when the left button is pressed we force the repaint
if (event->button() == Qt::LeftButton)
{
isPressed = true;
m_status = PRESSED;
update();
}
} void CustomPushButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && isPressed)
{
isPressed = false;
m_status = NORMAL;
emit clicked();
}
} void CustomPushButton::enterEvent(QEvent* event)
{
isPressed = false;
m_status = HOVER;
} void CustomPushButton::leaveEvent(QEvent* event)
{
isPressed = false;
m_status = NORMAL;
} void CustomPushButton::setBtnBackground(const QString& path)
{
m_imagePath = path;
// resize the button to fit the background picture.
setFixedSize(QPixmap(m_imagePath).size());
}

  在CPP文件中的主要工作是,根据不同的按钮状态来设置不同背景图,这样才能实现不同状态的切换。注意在setBtnBackground()中设置了按钮的尺寸。这里是根据按钮图片的大小来设置的。否则的话容易导致图片大小和按钮大小不一致的现象。这样,一个自定义的按钮类就实现了。在主窗口中的调用方式:

//////////////////////////////////////////////////////////////////////////
// initialize top banner
m_topLayout = new QHBoxLayout(this); // banner的水平布局管理器
m_windowTitle = new QLabel(QStringLiteral("Trojan Assessment Platform"), this); // banner左边的文本
QFont font = const_cast<QFont&>(m_windowTitle->font());
font.setBold(true);
font.setPointSize(10);
m_windowTitle->setFont(font);
m_windowTitle->setObjectName("WhiteLabel"); // 设置object name,便于在QSS文件中使用选择器 m_settings = new CustomPushButton(this); // 设置按钮
m_minBtn = new CustomPushButton(this); // 最小化按钮
m_closeBtn = new CustomPushButton(this); // 关闭按钮
m_settings->setBtnBackground(QStringLiteral(":/SysButtons/menu")); // 设置按钮的背景图片,下同
m_settings->setToolTip(QStringLiteral("Settings")); // 设置文本提示,下同
m_minBtn->setBtnBackground(QStringLiteral(":/SysButtons/min"));
m_minBtn->setToolTip(QStringLiteral("Minimize"));
m_closeBtn->setBtnBackground(QStringLiteral(":/SysButtons/close"));
m_closeBtn->setToolTip(QStringLiteral("Close")); m_topLayout->addWidget(m_windowTitle, 0, Qt::AlignVCenter); // 文本是垂直居中的
m_topLayout->addStretch();
m_topLayout->addWidget(m_settings, 0, Qt::AlignTop);
m_topLayout->addWidget(m_minBtn, 0, Qt::AlignTop);
m_topLayout->addWidget(m_closeBtn, 0, Qt::AlignTop);
m_topLayout->setSpacing(0); // 组件之间没有空隙,这样按钮与按钮之间看起来就没有间隔了
m_topLayout->setContentsMargins(10, 0, 10, 0); // 这里设置的是整个layout与其他layout之间的margin,而spacing是layout内部组件之间的间距

  效果如下:

主窗口背景

      从上面的截图我们可以发现,无论是360安全卫士还是金山卫士,头部banner都有一个背景图。这个背景图是如何添加的呢?一种实现是方式是,为整个主窗体添加一个背景图,在背景图的基础上再留出一块区域放置central widget。这种效果对比如下:

好了,这下就可以中间主体部分放置任何想放的控件了。关键的实现代码:

TrojanAssessment::TrojanAssessment(QWidget *parent)
: ShadowWindow(parent)
{
// layout for main widget
m_mainLayout = new QVBoxLayout(this); /* set the width and height of the window fixed. */
setFixedSize(900, 600); splitter = new QSplitter(Qt::Horizontal, this);
splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
splitter->setHandleWidth(1); // create title widget and status bar
titleWidget = new TitleWidget(this);
// remember the time when the program start
login_dt = QDateTime::currentDateTime();
restoreSettings(); // settings for main layout
m_mainLayout->addWidget(titleWidget);
m_mainLayout->addWidget(splitter);
m_mainLayout->addLayout(m_bottomLayout);
m_mainLayout->setSpacing(0);
m_mainLayout->setContentsMargins(5, 5, 5, 5);
setLayout(m_mainLayout); } void TrojanAssessment::paintEvent(QPaintEvent* event)
{
// First, we pass the paint event to parent widget to draw window shadow.
// Then, we do our specific painting stuff.
ShadowWindow::paintEvent(event);
// draw the background using the specified image.
QPainter painter(this);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::white);
painter.drawPixmap(5, 5, width()-10, height()-10, QPixmap(":/background/title_background")); // 设置主窗体的背景图片
}

状态栏

      QMainWindow自带一个状态栏,这个状态栏类(QStatusBar)的一些方法可用于设置状态栏上的组件、文本等,并可进行自由组合。我们这里的处理很简单,仅仅是添加了一个图标和一个文本,具体的代码很简单:

icon_label = new QLabel(this);
icon_label->setPixmap(QPixmap(":/menu/cloud"));
icon_label->setFixedSize(QPixmap(":/menu/cloud").size());
lastrun_label = new QLabel(this);
m_bottomLayout = new QHBoxLayout(this);
m_bottomLayout->addStretch();
m_bottomLayout->addWidget(icon_label, 0, Qt::AlignCenter);
m_bottomLayout->addWidget(lastrun_label, 0, Qt::AlignCenter);
m_bottomLayout->setSpacing(5);
m_bottomLayout->setContentsMargins(0, 3, 10, 3);

  由于我们主窗体是一个自定义大小的窗体,所以我们并没有使用到和QStatusBar相关的方法。由上面的窗口的布局也可以看得出来,这里的状态栏是分割出来的主窗体的一部分。使用水平布局管理器也很容易构造出复杂的布局。

代码

请访问:https://github.com/csuft/QTrojanAssessment

小结

      本文讲解了如何构建一个符合“业界标准”的软件界面,重点在主窗体的布局设计。后续的博文将讲解如何添加central widget及添加banner中的工具箱。

       

用Qt写软件系列五:一个安全防护软件的制作(1)的更多相关文章

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

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

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

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

  3. 系列五AnkhSvn

    原文:系列五AnkhSvn AnkhSvn介绍 AnkhSVN是一款在VS中管理Subversion的插件,您可以在VS中轻松的提交.更新.添加文件,而不用在命令行或资源管理器中提交.而且该插件属于开 ...

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

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

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

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

  6. tensorflow笔记(五)之MNIST手写识别系列二

    tensorflow笔记(五)之MNIST手写识别系列二 版权声明:本文为博主原创文章,转载请指明转载地址 http://www.cnblogs.com/fydeblog/p/7455233.html ...

  7. Qt写的截图软件包含源代码和可执行程序

    http://blog.yundiantech.com/?log=blog&id=14 Qt写的截图软件包含源代码和可执行程序 http://download.csdn.net/downloa ...

  8. <p>在我们的实际软件项目中,管理团队事实上比写代码或者实现一个客户的需求更为的有挑战性。由于编程实际上是和机器打交道,而和机器打交道,仅仅要你符合机器预定的逻辑,</p>

    在我们的实际软件项目中,管理团队事实上比写代码或者实现一个客户的需求更为的有挑战性. 由于编程实际上是和机器打交道.而和机器打交道,仅仅要你符合机器预定的逻辑, 一步步迈向解决这个问题的道路上一点都不 ...

  9. SAP ECC6安装系列五:安装后 License 的处理

    原作者博客 http://www.cnblogs.com/Michael_z/ ======================================== 我发现我确实比较懒,先和各位说声抱歉了 ...

随机推荐

  1. VS开发中的代码编写小技巧——避免重复代码编写的几种方法

    上一篇文章中程序员的幸福生活--有你的日子,每天都是情人节,收到了大家的很多好评.鼓励和祝福,非常感动,真诚的谢谢大家.也希望每个朋友都能保持一个积极向上的心态,去迎接丰富多彩的人生. 在开发过程中, ...

  2. java提高篇(六)-----使用序列化实现对象的拷贝

    我们知道在Java中存在这个接口Cloneable,实现该接口的类都会具备被拷贝的能力,同时拷贝是在内存中进行,在性能方面比我们直接通过new生成对象来的快,特别是在大对象的生成上,使得性能的提升非常 ...

  3. 如何给开源的DUILib支持Accessibility

    最近的工作是给开源的DUILib支持Accessibility, 一些经验记录并分享下. 微软的Accessibility其实Windows平台上一个挺重要的东西, 尽管在国内不受重视,但是如果你的软 ...

  4. [异常解决] ubuntu上安装虚拟机遇到的问题(vmware坑了,virtual-box简单安装,在virtual-box中安装精简版win7)

    利用周末时间将整个电脑格式化,换成了ubuntu系统- 所谓:扫清屋子再请客! 但是有些软件只在win上有,于是还是考虑装个虚拟机来个——逐步过度策略,一点点地从win上转移到linux上 我的系统是 ...

  5. Linux网络编程系列-TCP编程实例

    实例: client #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #inc ...

  6. NodeMCU初探

    对于ESP8266模块,早就想知道如何用其脚本语言, 自己先用的这个模块测试的 首先是先下载需要用到的工具和固件 链接:http://pan.baidu.com/s/1dF5NZ3N 密码:bziq ...

  7. Zabbix安装部署

    zabbix服务器端配置:(10.27.98.29服务器端) 1.安装lamp环境 yum -y install autoconf curl-devel gcc gcc-c++ httpd httpd ...

  8. iOS开发——高级语法篇&继承、实现、依赖、关联、聚合、组合的联系与区别

    继承.实现.依赖.关联.聚合.组合的联系与区别 分别介绍这几种关系: 继承 指的是一个类(称为子类.子接口)继承另外的一个类(称为父类.父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者 ...

  9. HashMap和Hashtable的区别 源码分析

    一:以前只知道HashMap和HashTable区别,死记硬背的记住HashMap 允许key value为空 而Hashtable 不允许为空 HashMap线程是非线程安全的,而Hashtable ...

  10. G2 DT时代的图形语法 正式发布

    G2有一个高大上的名字叫做:The Grammar Of Graphics——图形语法.它是一个强大的语义化图表生成工具,它提供了一整套图形语法,可以让用户通过简单的语法搭建出无数种图表,并且集成了大 ...