用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!

參考

  1. QSS   
  2. http://stackoverflow.com/questions/4521252/qt-msvc-and-zcwchar-t-i-want-to-blow-up-the-worl
  3. http://www.qtcn.org/bbs/read-htm-tid-30828.html
  4. http://blog.csdn.net/dbzhang800/article/details/6707152


免責聲明!

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



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