用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中的工具箱。

       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM