前言
在上一篇中,我們基本上完成了主要功能的實現,剩下的一些導出、進程子模塊信息等功能,留到后面再來慢慢實現。這一篇來講述如何對主界面進行個性化的定制。Qt庫提供的只是最基本的組件功能,使用這些組件開發出來的軟件基本上個性可言。如果開發的產品只講究實用性,那么UI體驗尚可擱置一邊。如果要面向客戶推廣部署,那么改善一下UI視覺效果對於產品的推廣也會有莫大的幫助。閑話不多說。先來對比一下界面個性化定制前后的效果:


先不說界面美化之后,界面有多絢麗、震撼人心。但是,突出產品主題、彰顯個性這塊倒是不折不扣。UI設計畢竟是一門學問,不然也不會有視覺交互師這種職業了。那么,如何用Qt來對軟件界面進行美化呢?
界面個性化定制
Qt開發中有兩種方法來進行UI定制:Qt二維繪圖(Qt 2D drawing and painting)以及Qt樣式表(Qt Style Sheet)。通常這兩種方法需要結合一起使用,以發揮其強大的作用。下面,我們就一起來看看,如何開始變身。
標題組件
首先對比一下標題欄前后的不同:

那么如何做到這樣呢?Qt提供的窗口都自帶了三個默認的按鈕:放大、縮小、關閉。而我們只有兩個按鈕:縮小、關閉。顯然,按鈕的繪制需要我們手動干涉。那么,手動繪制的話繪制到哪里去呢?通過什么方法呢?怎么實現默認按鈕的功能呢?看下一張圖我們似乎神馬都明白了:

整個一“窗中窗”啊!也就是說,我把默認的窗口邊框給去掉了,什么標題啊,按鈕啊都是自己手動繪制的。怎么繪制的呢?這其實也簡單,通過窗口布局管理器啊。這么一規划,整個窗口就可以這樣去實現了:

不過,我們得找到幾張按鈕狀態背景圖,分別對應不同的按鈕狀態(按下、懸停、正常)。然后重寫鼠標事件(mouseMoveEvent, mousePressedEvent, enterEvent, leaveEvent等)來切換按鈕的背景圖,這樣就實現了按鈕的不同狀態。當然,這些都需要Qt繪圖類的參與。幾個比較重要的繪圖類:QPainter, QPixmap, QColor,……,尤其是QPainter類及QPainterPath等,恰當的使用能帶來繪圖質量的大幅提高。
窗口內容布局
由上面的規划圖可以看出,內容布局由三個部分組成上方(top layout)的行編輯框、兩個按鈕,中間及下面的兩個QTableView。那么就先看看上方的top layout怎么個實現。這倒簡單,一個行編輯框(QLineEdit)、兩個下推按鈕(QPushButton),用水平布局管理器一拉就完成了。那么如何進行美化了? 我是這么做的,C++代碼部分:
1 m_filterexp = new QLineEdit(this); 2 m_filterexp->setPlaceholderText(QStringLiteral("Filter expression")); 3 m_filterexp->setContentsMargins(5, 0, 3, 1); 4 m_refreshBtn = new QPushButton(QStringLiteral("Refresh"), this); 5 m_exportBtn = new QPushButton(QStringLiteral("Export..."), this); 6 m_refreshBtn->setObjectName("refreshBtn"); 7 m_exportBtn->setObjectName("exportBtn"); 8 m_refreshBtn->setFixedSize(75, 25); 9 m_exportBtn->setFixedSize(75, 25); 10 m_filterexp->setFixedHeight(25);
余下的工作交給Qt Style Sheet來做吧。我們在上面設置了按鈕的Object name,這里的QSS選擇器就用#來選擇,相當於CSS里面的ID選擇器。
1 QPushButton#refreshBtn, QPushButton#exportBtn { 2 border-radius: 2px; 3 border: 1px solid rgb(89, 153, 48); 4 background:transparent; 5 color: green; 6 } 7 8 QPushButton#refreshBtn:hover { 9 background: #86BA10; 10 } 11 12 QPushButton#exportBtn:hover { 13 background: #86BA10; 14 }
正常狀態我們僅僅用淡綠色給他們描個邊,背景色設置為透明,圓角2個像素,當鼠標懸停在按鈕上面的時候,我們就用淡綠色繪制按鈕背景。效果如下,就這樣吧,簡單大方。而中間部分的兩個QTableView是重點。


QTableView的美化
QTableView分成表頭(Header)和表體(body)兩部分。對於表頭,我們需要做的不多,僅僅是換下背景色,去掉分節虛線,隱藏掉垂直表頭。於是:
1 m_procssTableView->verticalHeader()->hide(); 2 m_procssTableView->horizontalHeader()->setSectionsClickable(false); 3 m_procssTableView->horizontalHeader()->setStretchLastSection(true); 4 m_procssTableView->setSelectionBehavior(QAbstractItemView::SelectRows); 5 m_procssTableView->setSelectionMode(QAbstractItemView::SingleSelection); 6 m_procssTableView->setEditTriggers(QAbstractItemView::NoEditTriggers); 7 m_procssTableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); 8 m_procssTableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); 9 m_procssTableView->setShowGrid(false); // disable the table grid. 10 m_procssTableView->verticalHeader()->setDefaultSectionSize(25); // set row height. 11 m_procssTableView->horizontalHeader()->setHighlightSections(false); 12 m_procssTableView->setFrameShape(QFrame::NoFrame); 13 m_procssTableView->setItemDelegate(new NoFocusFrameDelegate());
表體部分,我們需要去掉網格線,這樣看起來更加簡潔。一格格的被網格線分開反而覺得被束縛了。其他的就是一些常見的設置選項,不必多說。另外要注意的是,我們總可以看到即便去掉了網格線,當我們鼠標點擊某一行時,Qt仍然會在鼠標下的單元格周圍畫上一個選線框。這看起來就像白玉中的一點瑕疵,忍不住就要把它摳出去。網上對此的做法是,自定義一個條目委托(Item Delegate),並重寫paint()方法:
1 void NoFocusFrameDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 2 { 3 QStyleOptionViewItem itemOption(option); 4 // remove the focus state 5 if (itemOption.state & QStyle::State_HasFocus) 6 { 7 itemOption.state ^= QStyle::State_HasFocus; 8 } 9 QStyledItemDelegate::paint(painter, itemOption, index); 10 }
上面的代碼很簡單,僅僅是去掉了State_HasFocus這個狀態,繪制工作仍然交由委托實現。
QTableView的上下文菜單,則需要重寫contextMenuEvent()實現。上下文的菜單項背景色仍然可以用QSS進行控制。另外,QTableView還有一個單元格對齊的問題。QTableView的默認顯示都是左對齊。這時,如果要想某一列都是居中對齊該怎么辦那?答案是從QStandardItemModel類派生一個子類,重寫虛函數data()。為什么不是從QTableView繼承呢?因為我們使用了Qt中的MVC框架。View只管繪制Model中的數據,至於數據內容、格式設置什么的,都在Model里面設置。因此,使用MVC的時候我們大部分工作需要和Model打交道。
話又說回來。這個data()函數帶兩個參數,第一個參數可以控制那幾列(行)怎么對齊。第二個參數是一個Role類型,用於區分不同的數據類型。因為Qt里面的數據分很多種:

我們得指明,當數據是用來顯示在單元格中的時候,我們才設置對齊方式啊。不然的話就會亂套了。總之,QSS和2D繪圖用好了,界面的效果也會慢慢炫起來。如果自己能夠做出精美的界面素材,那么更加是錦上添花了。
遇到的問題
wchar_t的問題。由於底層使用了Windows API實現,免不了要和寬字符打交道。於是用上了QString類的兩個靜態方法:fromStdString(), fromStdWString()。用來將標准的string和wstring類型轉換為QString類型。但是在鏈接的時候出錯了:

fromStdWString無法解析的外部符號!解決方案如下:后面也有一些鏈接,至於為什么,我也一直沒看懂。

截圖及代碼

view it on Github:click me!
