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

引言

又有许久没有更新了。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相关的方法。由上面的窗口的布局也可以看得出来,这里的状态栏是分割出来的主窗体的一部分。使用水平布局管理器也很容易构造出复杂的布局。

小结

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

       

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

时间: 2024-10-05 23:50:19

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

用Qt写软件系列六:博客园客户端的设计与实现(用Fiddler抓包,用CURL提交数据,用htmlcxx解析HTML)

引言 博客园是本人每日必逛的一个IT社区.尽管博文以.net技术居多,但是相对于CSDN这种业务杂乱.体系庞大的平台,博客园的纯粹更得我青睐.之前在园子里也见过不少讲解为博客园编写客户端的博文.不过似乎都是移动端的技术为主.这篇博文开始讲讲如何在PC端编写一个博客园客户端程序.一方面是因为本人对于博客园的感情:另一方面也想用Qt写点什么东西出来.毕竟在实践中学习收效更快. 登录过程分析 登录功能是一个客户端程序比不可少的功能.在组装Http数据包发送请求之前,我们得看看整个登录是怎样一个过程.F

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

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

一个用pyton写的监控服务端进程的软件hcm

使用udp实现,简单,方便,不用三次握手 1. 所有部署服务器进程的机器有一个代理进程hagent,用来监听hcm console中发送过来的命令 2.hcm需要提供以下命令 start :普通方式启动进程 stop :停止进程 reload :重新载入配置文件 resume :恢复方式启动进程(共享内存) tail:查看日志的末尾 deploy:直接更新服务器文件 rmshm:删除共享内存 autoresume:开启和关闭某个进程的自动拉起功能 3. hcm 监控所有的进程,发现进程不在时,以

用qt写的一个简单到不能在简单的上位机

学QT时,写的一个简单得不能再简单的串口上位机,用来控制单片机上的2个LED.假设一个是只有开和关的状态.一个可以调节亮度.上位机的界面如下图: 其中,波特率,数据位,停止位下拉值在设计师里面添加.剩下的功能,基本由代码实现.通信使用的协议也是随便写的.很简单和随意.图片是老弟手绘的. 下面贴代码 (*^__^*) #include "mainwindow.h" #include "ui_mainwindow.h" #include <QtSerialPort

So Easy! Oracle在Linux上的安装配置系列五

So Easy! Oracle在Linux上的安装配置系列五 本篇是监听器的配置的续篇,上一小节我们创建了一个监听器,创建了密码文,在监听和实例都启动的情况下,从远程windows即时客户端连接到了oracle服务器.本篇我将继续说监听器,还将完成相关的实验 以下内容整理自网络 Oracle 监听器 Listener 是一个重要的数据库服务器组件,在整个 Oracle 体系结构中,扮演着重要的作用.它负责管理 Oracle 数据库和客户端之间的通讯,它在一个特定的网卡端口(默认是TCP 1521

S5PV210开发系列五_sd卡驱动实现

S5PV210开发系列五 sd卡驱动实现 象棋小子    1048272975 SD卡(Secure Digital Memory Card)具有体积小.容量大.数据传输快.可插拔.安全性好等优点,被广泛应用于便携式设备上.例如作为数码相机的存储卡,作为手机.平板多媒体扩展卡用的TF卡(micro sd).笔者此处就S5PV210的 sd卡驱动实现作一个简单的介绍. 1. sd卡概述 sd卡技术是在MMC卡的基础上发展起来的,其尺寸与MMC卡一样,只是比MMC卡厚了0.7mm,因此sd设备可以识

Qt的信号槽,一个老MFC的经验

最近在利用闲暇时间研究Qt,大概有3周了,看过了官网的white paper并浏览了一遍<C++ GUI Programming with Qt 4, 2nd Edition>.总的来说,感触还是很深的,所以今天想写点东西,作为对Qt初体验的记录. 本人作为一个MFC老古董(如果你关注本博的话,你是知道的,汗-),发现研究Qt是一件非常令人赏心悦目的事情.那感觉,就像你逛完了集美家具城看到一堆国产风格的家具后突然走进宜家的卖场一样.我并不是说宜家的家具有多么好,只不过宜家家具代表的北欧风格和家

RX系列五 | Schedulers线程控制

RX系列五 | Schedulers线程控制 在我们上一篇文章中的,我们的小例子里有这么一段代码 //网络访问 .observeOn(Schedulers.io()) 事实上,我们在使用网络操作的时候,便可以控制其运行在哪个线程中,而Schedulers类,有四个方法,分别是 Schedulers.immediate(); Schedulers.newthread(); Schedulers.io(); Schedulers.computation(); 以及RxAndroid中的Android

Maven 系列 五 :使用Nexus搭建Maven私服

1 . 私服简介 私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件.有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库:否则,私服请求外部的远程仓库,将构件下载到私服,再提供给本地仓库下载.                                                  我们可以使用专门的 Maven 仓库管理软件来搭建私服,比如:Apache Archiva,Artifactory,Sonatype Nexus.这