導讀
正如web前端開發中CSS(Cascade Style Sheet)的作用一樣,Qt開發中也可以使用修改版的QSS將邏輯業務和用戶界面進行隔離。這樣,美工設計人員和邏輯實現者可以各司其職而不受干擾。更重要的是,由於界面和邏輯處理是分離的,低耦合性使得代碼重構的工作量可以減少到最小。QSS和CSS的語法幾乎一致,除了Qt自身增加的一些屬性之外,其余的屬性都可以在CSS2或CSS3中找到對應的屬性。因此,如果曾經有過CSS的使用經驗,那么QSS的使用將游刃有余。關於QSS的使用實踐,打算撰寫一系列博客來記錄使用過程中的一些技巧和方法。本篇是系列第一篇,主要探討QPushButton及QMenu在QSS的作用下的效果。
QSS介紹
QSS(Qt Style Sheet)借鑒於CSS的良好思想,實現了界面和邏輯的分離。QSS中引入了盒模型(Box Model)概念,這是樣式表技術中的核心概念之一。具體的解釋網上說的挺多的,Qt本身自帶的文檔也有較為詳細的說明。在使用盒模型進行設計之前,我們得了解下Qt中哪些組件可以用盒模型進行布局設計:
QCheckBox | QCheckBox的勾選符號可以使用::indicator子組件來定制。默認情況下,勾選標記位於組件矩形的左上角。QCheckBox的spacing屬性可以用於指定勾選標記和文本內容之間的間距。 |
QComboBox | 對於QComboBox而言,支持盒模型的其實是包裹QComboBox的外框(Frame),QComboBox的下拉單按鈕通過::drop-down子組件來定制,默認情況下下拉單按鈕位於盒模型中padding矩形的右上角。下拉按鈕中的箭頭號通過::down-arrow子組件進行定制,箭頭號默認位於子組件的正中央。 |
QGroupBox | QGroupBox的標題用::title子組件進行定制,標題的位置依QGroupBox::textAlignment的具體值而言。對於可選的QGroupBox而言,標題中還會包含一個勾選標記,勾選標記用::indicator來定制,spacing仍然用於設置勾選標記與文本的間距。 |
QSpinBox(QDateEdit,QDateTimeEdit) | ![]() |
QToolBox | QToolBox是一個具備QQ折疊功能的組件,因此其中的獨立的page使用::tab子組件定制。::tab組件支持一些偽狀態::only-one, :first, :middle, :previous-selected, :next-selected, :selected,從而達到定制特定page的目的。 |
QMenuBar | 菜單欄組件的spacing屬性可指定菜單項之間的間距,單個菜單項還可以通過::item子組件定制風格。但是值得注意的是,由於MAC下菜單欄集成到了系統菜單欄,此時樣式表會失去作用。 |
QProgressBar | 進度條組件使用::chunks子組件來定制進度條樣式,text-align屬性用於設定進度條中文本的對齊方向:left, center, right |
QScrollBar | 滾動條的組成其實非常復雜,依據垂直和水平方向的不同,由::handle, ::add-line, ::sub-line, ::add-page, ::sub-page, ::right-arrow, ::left-arrow, ::down-arrow, ::up-arrow等子組件組成。偽狀態:horizontal, :vertical用於確定滾動條的方向,width(min-width), height(min-height)則可確定滾動條的不同長和寬。 |
QToolBar | 工具欄的偽狀態:top, :left, :right, :bottom的使用依賴於工具欄的具體位置;而:first, :last, :middle, :only-one則用於指代工具欄中的具體位置。工具欄的分隔器用::separator子組件指代,::handle則指代移動工具欄的handle. |
QMenu | 菜單中的獨立項使用::item子組件定制,除了常見的偽狀態,::item還支持:selected, :default, :exclusive以及:non-exclusive等偽狀態。利用這些偽狀態,可以為不同狀態的菜單項定制出不同的外觀。對於可勾選的菜單項,使用::indicator對勾選標記進行定制,::separator則定制菜單項之間的分隔符;對於有子菜單的菜單項,其箭頭號可以用::right-arrow, ::left-arrow進行定制,還有::scroller及::tearoff兩個子組件,暫時沒搞清楚具體作用。 |
QLabel | QLabel不支持:hover偽狀態,自Qt4.3開始,給QLabel設置樣式表也就隱式指定了QFrame::frameStyle屬性。 |
QLineEdit | 對於QLineEidt,selection-color, selection-background-color屬性分別指定了選中文本的文本顏色和背景色,lineedit-password-character屬性說明密碼輸入顯示的字符。將在后面的實踐中說明。 |
QPushButton | 支持:default, :flat, :checked偽狀態,對於具備關聯菜單的按鈕,可以用::menu-indicator來定制下拉菜單標記。而:open和:closed偽狀態則分別用於定制菜單打開和關閉時按鈕的外觀。 |
QRadioButton | 同上,::indicator用於定制文本前面的選項框,spacing指定文本與選項框之間的間距。 |
QSlider | 對於水平的QSlider,min-width和height屬性必須同時提供;對於垂直的QSlider, 必須同時提供min-height和width屬性。QSlider由::groove和::handle兩部分組成。::groove子組件是一條槽,供::handle在上面滑動。 |
QSplitter | 窗體分割器,主要的部件是::handle。通過::handle可以動態改變分割器中的不同子窗口大小。 |
QTextEdit | 使用selection-color, selection-background-color屬性定制,其他的定制方式見QAbstractScrollArea。 |
QToolButton | 如果QToolButton關聯了一個菜單,那么和QPushButton是相同的處理方式。如果被設置成了QToolButton::MenuButtonPopup模式,那么::menu-button用於繪制菜單按鈕,而::menu-arrow用於繪制按鈕中的箭頭號。注意:如果設置了QToolButton的背景色,那么必須還要設置邊框的寬度才會起作用。這是因為QToolButton默認繪制的邊框會完全遮擋住用戶設置的背景色。 |
QAbstractScrollArea | 所有派生自QAbstractScrollArea類的子類,包括QTextEdit, QAbstractItemView,都可以通過設置background-attachment屬性來實現可滾動背景。通過給background-attachment設置fixed和scroll,背景會固定不動或者跟隨滾動。 |
QHeaderView | QHeaderView是Model/View框架中的一部分,最重要的子組件是::section,::section支持:middle, :first, :last, :only-one, :next-selected, :previous-selected, :selected, :checked等偽狀態。::up-arrow和::down-arrow用於定制表頭的排序標記。 |
QListView(QListWidget) | show-decoration-selected屬性控制選中時是選中整項還是僅僅只是項的文本,其他和QTableView相同。 |
QTableView(QTableWidget) | 當view支持斑馬色條時,alternate-background-color屬性指定備選色實現斑馬色帶,selection-color和selection-background-color屬性指定選定項的文本色和背景色。注意:保證同時設置了背景色和邊框寬度值。 |
QTreeView(QTreeWidget) | show-decoration-selected屬性控制選中時是選中整項還是僅僅只是項的文本, 子組件::branch和::item用於精細化控制。 |
應用實例
下面看看如何用QSS對按鈕及其關聯菜單進行外觀定制。我們首先用如下的代碼初始化好按鈕及其關聯菜單,並在Windows 7默認主題下看看其效果:
ui.serviceType->setFixedWidth(95); m_mainMenu = new QMenu(this); m_osSubMenu = new QMenu(this); m_appSubMenu = new QMenu(this); m_details = new QAction(QStringLiteral("Details"),this); m_details->setCheckable(true); m_details->setChecked(true); m_settings = new QAction(QStringLiteral("Settings"), this); m_settings->setIcon(QIcon(":/misc/preference")); m_settings->setShortcut(QKeySequence::Print); m_os = new QAction(QStringLiteral("OS"), this); m_app = new QAction(QStringLiteral("Applications"), this); m_github = new QAction(QStringLiteral("Github"), this); m_github->setIcon(QIcon(":/app/github")); m_github->setShortcut(QKeySequence("Ctrl+G")); m_amazon = new QAction(QStringLiteral("Amazon"), this); m_amazon->setIcon(QIcon(":/app/amazon")); m_photoshop = new QAction(QStringLiteral("Photoshop"), this); m_photoshop->setIcon(QIcon(":/app/photoshop")); m_facebook = new QAction(QStringLiteral("Facebook"), this); m_facebook->setIcon(QIcon(":/app/facebook")); m_apple = new QAction(QStringLiteral("Apple"), this); m_apple->setShortcut(QKeySequence("Ctrl+A")); m_apple->setIcon(QIcon(":/os/apple")); m_windows = new QAction(QStringLiteral("Windows"), this); m_windows->setIcon(QIcon(":/os/windows")); m_windows->setShortcut(QKeySequence("Ctrl+W")); m_fedora = new QAction(QStringLiteral("Fedora"), this); m_fedora->setIcon(QIcon(":/os/fedora")); m_fedora->setDisabled(true); m_osSubMenu->addAction(m_apple); m_osSubMenu->addAction(m_windows); m_osSubMenu->addAction(m_fedora); m_os->setMenu(m_osSubMenu); m_appSubMenu->addAction(m_amazon); m_appSubMenu->addAction(m_github); m_appSubMenu->addAction(m_facebook); m_appSubMenu->addSeparator(); m_appSubMenu->addAction(m_photoshop); m_app->setMenu(m_appSubMenu); m_mainMenu->addAction(m_details); m_mainMenu->addSeparator(); m_mainMenu->addAction(m_os); m_mainMenu->addAction(m_app); m_mainMenu->addAction(m_settings); ui.serviceType->setMenu(m_mainMenu);
先不加任何QSS效果,其效果如下:
一片灰蒙蒙的感覺,不亮堂。對於講究實用性的軟件產品,做到這一步已然足夠。如若客戶要求具備個性一點的外觀呢?此時此刻,我們可以嘗試用QSS來進行改造。我們將所有的樣式語句放到一個*.qss文件中,然后在main函數中加載。需要注意的是,我們應該將.qss文件添加到.qrc文件中進行編譯。每一次修改.qss文件之后應該重新編譯.qrc文件。否則在界面上將看不出任何改變。代碼如下:
QFile file(":/ThemeRoller/style"); file.open(QFile::ReadOnly); qApp->setStyleSheet(file.readAll()); file.close();
先考慮將QPushButton作為練手對象,編寫如下QSS代碼:
QPushButton { background: white; border: 1px solid rgb(41, 57, 85); border-radius: 3px; # 設置邊框具備3個像素的圓角 font-weight: bold; # 字體設置為加粗 } QPushButton:hover { background: lightgray; }
效果對比如下:
效果似乎還不錯,但是我們發現右邊的箭頭號已經偏移到右下角去了,不太和諧。我們嘗試使用subcontrol-position和subcontrol-origin兩個屬性來進行調整(position和origin這兩個屬性在CSS中是非常容易被混淆的,具體含義需細細區分):
QPushButton::menu-indicator{ subcontrol-position: right center; subcontrol-origin: padding; }
顯然,系統默認的箭頭號不太和諧,於是我們再嘗試換掉這個箭頭號,並且在菜單打開時設置為向下的箭頭號,菜單關閉時設置為水平向右的箭頭號:
QPushButton::menu-indicator:open { image: url(:/misc/down_arrow_2); subcontrol-position: right center; subcontrol-origin: padding; } QPushButton::menu-indicator:closed { image: url(:/misc/right_arrow_2); subcontrol-position: right center; subcontrol-origin: padding; }
得到的效果如下:
好吧,到此位置我們的按鈕似乎好看多了。再來看看整個關聯菜單的QSS該如何編寫。首先,把背景色調整為白色是必須的,如下:
QMenu { background-color: white; padding: 1px; # 縮小菜單項四個方向的padding } QMenu::item{ background-color: transparent; }
我們可以發現一個嚴重的缺陷,當鼠標划過相應的菜單項時,文本內容看不見了,顯然是由於背景色的原因,所以我們還得修改一下啊:
QMenu::item:selected{ background-color: rgb(234, 243, 253); color: black; }
用偽狀態:selected進行設置,當鼠標划過時將文本顏色設置為黑色,也即保持不變。但此時我們根本看不到鼠標划過的效果,因此給當前選中的菜單項一個背景色吧(rgb(234, 243, 253))。效果如何呢:
根據不同的需要,定制出來的外觀也是千差萬別的。主要是能理解好QSS中各種屬性的作用,其余的工作就是做好布局設計和圖片設計。美觀大方的界面設計離不開精致的圖標設計和合理的布局管理。