zoukankan      html  css  js  c++  java
  • 用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相关的方法。由上面的窗口的布局也可以看得出来,这里的状态栏是分割出来的主窗体的一部分。使用水平布局管理器也很容易构造出复杂的布局。

    代码

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

    小结

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

           

  • 相关阅读:
    几种跨平台解决方案:React Native、Kotlin、Flutter、Swift
    vue组件化
    flutter学习笔记(合集)
    sessionStorage获取用户行为
    什么是深度学习?
    javascript原生dom的那些事儿
    对象的创建
    初识requirejs
    使用vue-cli3快速构建项目
    python16_day40【数据结构】
  • 原文地址:https://www.cnblogs.com/csuftzzk/p/Trojan_Assessment_Platform_1.html
Copyright © 2011-2022 走看看