導讀
一款流行的軟件,往往會在功能漸趨完善的時候,通過改善交互界面來提高用戶體驗。畢竟,就算再牛逼的產品,躲藏在糟糕的用戶界面之后總會讓用戶心生不滿。界面設計需綜合考慮審美學、心理學、設計學等多因素,是一份精細活。這篇博文仍然以Qt的使用為主旨,探討一下在Qt中如何進行系統托盤的個性化定制。
介紹
首先我們看看幾款知名軟件的系統托盤設計:
上圖是金山衛士的系統托盤菜單設計。我們稍作分析:整個托盤菜單窗口是個半透明的設計,窗口邊框進行了圓角處理。底部的菜單項包含三個Button,倒數第二、三個菜單項的右部還加上了一個自定義的單選按鈕。頂部菜單項則包含一個評級組件;其他菜單項則沒有什么特別,加上對應的圖標即可完成設計。但是可能由於整個背景色的緣故,導致整體效果看起來灰蒙蒙的,不太亮堂。
上圖是360安全衛士的托盤菜單。頂部和底部的兩個菜單項都將背景色設置成了360安全衛士的主題色,加上了兩個標簽和按鈕。其他菜單項保持不變。另外,菜單的背景色也被設置成了白色。整個菜單的設計較為簡潔、清爽。雖然並不喜歡用360安全衛士,但是並不妨礙我對其產品外觀設計的贊賞。
原型設計
既然有了上述兩款產品的參考,我們也可以試着設計下自己的系統托盤。首先我們需要一個原型設計工具,將草圖繪制好我們才能用代碼將最終結果顯示出來。這里推薦一個原型設計工具:Balsqmiq mockup。這款工具使用簡單,其提供的原型組件非常豐富,使用會覺得非常方便。
根據初步設想,我設計了如下的一個原型草圖:
在布局方面基本上綜合了金山衛士和360安全衛士的設計特點。頂部菜單項部署兩個Label, 一個用來顯示應用程序的窗口標題或產品名稱,另一個顯示為go to visit,可以響應鼠標點擊事件。底部菜單項和金山衛士一樣,設置了三個按鈕:Update, about, exit,使用水平均勻布局。其他的菜單項則和普通菜單項沒有區別。 基本上,一個自定義的托盤菜單已經躍然而出。
代碼實現
根據上述的原型設計,我們要做的准備工作顯然就是准備好圖片。對於沒有美工技能的程序員來說,尋找界面圖片素材顯然是一大難題。做不出圖片顯然只好去網上搜索了。本人在網上下載了一堆的圖片壓縮包,有一個值得推薦:異次元圖標。另外還有一個圖片搜索網站也值得推薦。在這里我准備的圖片如下:
每個圖片都取了一個別名,這樣在代碼中我們直接使用圖片別名,從而消除與圖片具體名稱的藕合性。資源准備好之后我們需要開始編碼了。參考本人曾經寫過的一篇博文(使用Qt創建系統托盤),可以實現一個默認主題的系統托盤菜單。但是這里我們要實現自定義托盤菜單,我們從QSystemTray派生一個子類,並定義好相關的類成員變量:
QMenu* m_trayMenu; QWidget* m_topWidget; QWidgetAction* m_topWidgetAction; QLabel* m_topLabel; QLabel* m_homeBtn; QWidget* m_bottomWidget; QWidgetAction* m_bottomWidgetAction; QPushButton* m_updateBtn; QPushButton* m_aboutBtn; QPushButton* m_exitBtn; QAction* m_runOnSystemBoot; QAction* m_helpOnline; QAction* m_homePage; QAction* m_notification; QAction* m_settings;
顯然,我們注意到一個平時沒有接觸到的:QWidgetAction。這個類自Qt 4.2引入,繼承自QAction。根據類名也可以推測出其含義:使用QWidget來充當Menu的Action。於是,我們似乎明白了自定義菜單的精髓:用Widget來做Action。這里我們主要定義頂部菜單項和底部菜單項。因此我們定義了兩個QWidgetAction。另外,我們還有一個疑問就是:布局好的Widget如何"偽裝"成Action插入到菜單項中去呢?我們可以使用QWidgetAction的setDefaultWidget()方法來完成這項工作。后面的代碼將會有說明。
此外,我們還注意到:360安全衛士的底部菜單項和頂部菜單項的背景色都是綠色的這又該如何實現呢?一種可行的方法是,安裝一個事件過濾器(Event Filter)。當過濾到繪制事件並且繪制的組件是頂部菜單項和底部菜單項時,我們改變繪制方式。代碼如下:
bool SystemTray::eventFilter(QObject *obj, QEvent *event) { if (obj == m_topWidget && event->type() == QEvent::Paint) { QPainter painter(m_topWidget); painter.setPen(Qt::NoPen); painter.setBrush(QColor(42, 120, 192)); painter.drawRect(m_topWidget->rect()); } return QSystemTrayIcon::eventFilter(obj, event); }
在完成了我們自己的繪制工作之后,還得再調用父類的事件過濾器,以免漏掉其他過濾工作。eventFilter()是一個protected方法,我們要在頭文件中進行重寫。
接下來要做的工作就是完成頂部和底部菜單項的繪制工作。先看看頂部菜單項如何繪制:
void SystemTray::createTopWidget() { m_topWidget = new QWidget(); m_topWidgetAction = new QWidgetAction(m_trayMenu); m_topLabel = new QLabel(QStringLiteral("HUST Information Security Lab")); m_topLabel->setObjectName(QStringLiteral("WhiteLabel")); m_homeBtn = new QLabel(QStringLiteral("Visit")); m_homeBtn->setCursor(Qt::PointingHandCursor); m_homeBtn->setObjectName(QStringLiteral("WhiteLabel")); QVBoxLayout* m_topLayout = new QVBoxLayout(); m_topLayout->addWidget(m_topLabel, 0, Qt::AlignLeft|Qt::AlignVCenter); m_topLayout->addWidget(m_homeBtn, 0, Qt::AlignRight|Qt::AlignVCenter); m_topLayout->setSpacing(5); m_topLayout->setContentsMargins(5, 5, 5, 5); m_topWidget->setLayout(m_topLayout); m_topWidget->installEventFilter(this); m_topWidgetAction->setDefaultWidget(m_topWidget); }
我們聲明了兩個Label標簽,作用在上文已說明。然后用垂直布局管理器將兩個標簽分左右放置。注意語句:m_topWidget->installEventFilter(this)。這條語句完成了過濾器的安裝。指針this表明窗口事件將先發往當前類的eventFilter()方法進行處理,如果不處理再發往其他類的過濾器進行處理。底部菜單項的初始化大致類似:
void SystemTray::createBottomWidget() { m_bottomWidget = new QWidget(); m_bottomWidgetAction = new QWidgetAction(m_trayMenu); m_updateBtn = new QPushButton(QIcon(":/menu/update"), QStringLiteral("Update")); m_updateBtn->setObjectName(QStringLiteral("TrayButton")); m_updateBtn->setFixedSize(60, 25); m_aboutBtn = new QPushButton(QIcon(":/menu/about"), QStringLiteral("About")); m_aboutBtn->setObjectName(QStringLiteral("TrayButton")); m_aboutBtn->setFixedSize(60, 25); m_exitBtn = new QPushButton(QIcon(":/menu/quit"), QStringLiteral("Exit")); m_exitBtn->setObjectName(QStringLiteral("TrayButton")); m_exitBtn->setFixedSize(60, 25); QHBoxLayout* m_bottomLayout = new QHBoxLayout(); m_bottomLayout->addWidget(m_updateBtn, 0, Qt::AlignCenter); m_bottomLayout->addWidget(m_aboutBtn, 0, Qt::AlignCenter); m_bottomLayout->addWidget(m_exitBtn, 0, Qt::AlignCenter); m_bottomLayout->setSpacing(5); m_bottomLayout->setContentsMargins(5,5,5,5); m_bottomWidget->setLayout(m_bottomLayout); m_bottomWidgetAction->setDefaultWidget(m_bottomWidget); }
分別對三個按鈕設置了大小和圖標。具體的外觀樣式則使用了QSS來進行控制,因此我們還為每個按鈕設置了一個Object Name。這個Object Name在QSS中充當ID選擇器,便於樣式控制。那么樣式文件該如何編寫呢?具體參看如下所示:
QMenu{ background:white; border:1px solid lightgray; # 邊框為灰色 } QMenu::item{ padding:0px 20px 0px 20px; margin-left: 5px; height:25px; } QMenu::item:selected:enabled{ background: lightgray; # 菜單項選中時背景色設置為淺灰色 color: white; # 文本顏色設置為白色,否則看不清文本內容了 } QMenu::separator{ height:1px; background: lightgray; # 菜單分割線也設置為淺灰色 margin:2px 0px 2px 0px; } QMenu::item:selected:!enabled{ background:transparent; } QPushButton#TrayButton { border: none; # 無邊框按鈕 background: transparent; # 按鈕背景設置為透明,這樣不會受到默認主題顏色干擾 } QPushButton#TrayButton:hover { background: rgb(233, 237, 252); # 鼠標懸停時,按鈕背景色設為淡色 color: rgb(42, 120, 192); # 鼠標懸停時,文本顏色不變 }
基本上,使用上面的樣式設置就可完成基本樣式設置。其他代碼就不再詳細敘述。到此,我們的托盤菜單就完成了個性化定制工作。
效果圖
根據上述代碼,我們實現的最終效果圖如下:
前面也說過:界面設計是一門學問,綜合了設計學、心理學、審美學等多學科。要設計出讓人耳目一新的產品界面,需要設計師具備相當的設計功力。但不管最終設計的怎么樣,我們已經知道了,如何實現具備個人特點的托盤菜單!