1. 程式人生 > >用Qt寫軟體系列三:一個簡單的系統工具之介面美化

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

參考