用Qt寫軟體系列三:一個簡單的系統工具之介面美化
前言
在上一篇中,我們基本上完成了主要功能的實現,剩下的一些匯出、程序子模組資訊等功能,留到後面再來慢慢實現。這一篇來講述如何對主介面進行個性化的定製。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!