這篇博文主要講解360安全衛士標題欄的創建。關於標題欄,我想大家應該都非常熟悉了,其主要包括窗口關閉、最大化/還原、最小化等按鈕;但是標題欄的這些按鈕都是非常有特色的。
在我寫這篇博文之前,我就已經完成了類似360安全衛士標題欄的創建代碼,在開始寫代碼時,我仔細想了想360安全衛士主界面標題欄的構建方法,它是自繪的還是貼圖的?所以我特意在360論壇查了查它皮膚制作的方法,並在它的安裝目錄下的找到了這些按鈕的特定圖片(皮膚文件解壓),即由貼圖來做的。
既然知道了構建方法,那么就用代碼實現即可,我實現的效果圖如下(初始界面是這樣,標題欄的其他效果和360安全衛士的標題欄效果一樣):
標題欄也是自定義的部件(繼承於QWidget),在這個自定義的部件里,你想實現啥功能都可以。
一、部件構建
部件構建當然是創建子部件,設置子部件樣式,給標題欄設置布局管理等,這些也都是很基礎的。
標題欄由三個QLabel和五個QToolButton組成,這五個QToolButton即為標題欄最右邊的五個功能按鈕,首先當然是創建這些子部件了。

//創建子部件
void TitleBar::CreateWidget()
{
//圖像標簽--logo
m_pLabelIcon = new QLabel(this);
QPixmap objPixmap(":/image/360AboutLogo.png");
m_pLabelIcon->setPixmap(objPixmap.scaled(TITLE_H,TITLE_H));
//文本標簽--標題
m_pLabelTitle = new QLabel(this);
m_pLabelTitle->setText(QString("360 Safe Guard V8.5"));
//文本標簽--樣式版本
m_pLabelVersion = new QLabel(this);
m_pLabelVersion->setText(QString("Use Class Style"));
//設置鼠標形狀
m_pLabelVersion->setCursor(Qt::PointingHandCursor);
//按鈕--更換皮膚
m_pBtnSkin = new QToolButton(this);
//設置初始圖片
SetBtnIcon(m_pBtnSkin,eBtnStateDefault,true);
//按鈕--菜單
m_pBtnMenu = new QToolButton(this);
SetBtnIcon(m_pBtnMenu,eBtnStateDefault,true);
//按鈕--最小化
m_pBtnMin = new QToolButton(this);
SetBtnIcon(m_pBtnMin,eBtnStateDefault,true);
//按鈕--最大化/還原
m_pBtnMax = new QToolButton(this);
SetBtnIcon(m_pBtnMax,eBtnStateDefault,true);
//按鈕--關閉
m_pBtnClose = new QToolButton(this);
SetBtnIcon(m_pBtnClose,eBtnStateDefault,true);
//獲得子部件
const QObjectList &objList = children();
for(int nIndex=0; nIndex<objList.count();++nIndex)
{
//設置子部件的MouseTracking屬性
((QWidget*)(objList.at(nIndex)))->setMouseTracking(true);
//如果是QToolButton部件
if(0==qstrcmp(objList.at(nIndex)->metaObject()->className(),"QToolButton"))
{
//連接pressed信號為slot_btnpress
connect(((QToolButton*)(objList.at(nIndex))),SIGNAL(pressed()),this,SLOT(slot_btnpress()));
//連接clicked信號為slot_btnclick
connect(((QToolButton*)(objList.at(nIndex))),SIGNAL(clicked()),this,SLOT(slot_btnclick()));
//設置頂部間距
((QToolButton*)(objList.at(nIndex)))->setContentsMargins(0,VALUE_DIS,0,0);
}
}
}
子部件創建完之后,就要設置這些子部件的基本樣式了,我使用qss樣式表對其進行樣式設置,當然還有其他方法。

//設置子部件樣式(qss)
void TitleBar::SetWidgetStyle()
{
//設置標簽的文本顏色,大小等以及按鈕的邊框
setStyleSheet("QLabel{color:#CCCCCC;font-size:12px;font-weight:bold;}QToolButton{border:0px;}");
//設置左邊距
m_pLabelTitle->setStyleSheet("margin-left:6px;");
//設置右邊距以及鼠標移上去時的文本顏色
m_pLabelVersion->setStyleSheet("QLabel{margin-right:10px;}QLabel:hover{color:#00AA00;}");
}
最后就是創建布局管理器,把這些子部件加入到布局管理器中,我使用水平布局管理器,如下代碼所示:

//創建設置布局
void TitleBar::CreateLayout()
{
//水平布局
m_pLayout = new QHBoxLayout(this);
//添加部件
m_pLayout->addWidget(m_pLabelIcon);
m_pLayout->addWidget(m_pLabelTitle);
//添加伸縮項
m_pLayout->addStretch(1);
//添加部件
m_pLayout->addWidget(m_pLabelVersion);
m_pLayout->addWidget(m_pBtnSkin);
m_pLayout->addWidget(m_pBtnMenu);
m_pLayout->addWidget(m_pBtnMin);
m_pLayout->addWidget(m_pBtnMax);
m_pLayout->addWidget(m_pBtnClose);
//設置Margin
m_pLayout->setContentsMargins(0,0,VALUE_DIS,0);
//設置部件之間的space
m_pLayout->setSpacing(0);
setLayout(m_pLayout);
}
在這節中,設置按鈕圖片的函數為SetBtnIcon函數,該函數的原型如下所示:

void SetBtnIcon(QToolButton *pBtn,eBtnMoustState state,bool bInit=false);
其中pBtn代表被設置圖片的按鈕,而eBtnMoustState是一個枚舉值,代表該按鈕當前的狀態,枚舉定義如下所示:

//枚舉,按鈕狀態
enum eBtnMoustState{
eBtnStateNone,//無效
eBtnStateDefault,//默認值(如按鈕初始顯示)
eBtnStateHover,//鼠標移到按鈕上狀態
eBtnStatePress//鼠標按下按鈕時狀態
};
而bInit表示是否是初始化設置,因為在SetBtnIcon函數里需要獲得主界面最大化標志值,而這時候主界面窗口構造函數還沒完成,同時在SetBtnIcon函數里又需要獲得主界面窗口對象,因此會矛盾,所以使用了bInit標志值進行區分。
SetBtnIcon函數的定義如下代碼所示:

//設置按鈕不同狀態下的圖標
void TitleBar::SetBtnIcon(QToolButton *pBtn,eBtnMoustState state,bool bInit/*=false*/)
{
//獲得圖片路徑
QString strImagePath = GetBtnImagePath(pBtn,bInit);
//創建QPixmap對象
QPixmap objPixmap(strImagePath);
//得到圖像寬和高
int nPixWidth = objPixmap.width();
int nPixHeight = objPixmap.height();
//如果狀態不是無效值
if(state!=eBtnStateNone)
{
/*設置按鈕圖片
按鈕的圖片是連續在一起的,如前1/4部分表示默認狀態下的圖片部分,接后的1/4部分表示鼠標移到按鈕狀態下的圖片部分
*/
pBtn->setIcon(objPixmap.copy((nPixWidth/4)*(state-1),0,nPixWidth/4,nPixHeight));
//設置按鈕圖片大小
pBtn->setIconSize(QSize(nPixWidth/4,nPixHeight));
}
}

//獲得圖片路徑(固定值)
const QString TitleBar::GetBtnImagePath(QToolButton *pBtn,bool bInit/*=false*/)
{
QString strImagePath;
//皮膚按鈕
if(m_pBtnSkin==pBtn)
{
strImagePath = ":/image/SkinButtom.png";
}
//菜單按鈕
if(m_pBtnMenu==pBtn)
{
strImagePath = ":/image/title_bar_menu.png";
}
//最小化
if(m_pBtnMin==pBtn)
{
strImagePath = ":/image/sys_button_min.png";
}
//最大化/還原按鈕,所以包括最大化和還原兩張圖片
if(m_pBtnMax==pBtn)
{
//如果是初始設置或者主界面的最大化標志不為真(其中MainWindow::Instance()使用單例設計模式)
if(bInit==true || MainWindow::Instance()->GetMaxWin()==false)
{
//最大化按鈕圖片路徑
strImagePath = ":/image/sys_button_max.png";
}
else
{
//還原按鈕圖片路徑
strImagePath = ":/image/sys_button_restore.png";
}
}
//關閉按鈕
if(m_pBtnClose==pBtn)
{
strImagePath = ":/image/sys_button_close.png";
}
return strImagePath;
}
二、設置按鈕其他效果
各位在使用360安全衛士的時候,把鼠標移到關閉按鈕上或者使用鼠標按下關閉按鈕,其呈現不同的圖片以示區分,當然其他按鈕也一樣。那么是不是也和工具欄按鈕一樣,子類化一個按鈕了?不需要。使用事件過濾器,在標題欄部件中進行事件判斷和目標判斷即可。
首先是創建事件過濾器,代碼如下所示:

//創建事件過濾器
void TitleBar::CreateEventFiter()
{
m_pBtnSkin->installEventFilter(this);
m_pBtnMenu->installEventFilter(this);
m_pBtnMin->installEventFilter(this);
m_pBtnMax->installEventFilter(this);
m_pBtnClose->installEventFilter(this);
}
然后在標題欄部件中重寫eventFilter函數即可,代碼如下:

//事件過濾
bool TitleBar::eventFilter(QObject *obj, QEvent *event)
{
//按鈕狀態
eBtnMoustState eState = eBtnStateNone;
//判斷事件類型--QEvent::Enter
if (event->type() == QEvent::Enter)
{
eState = eBtnStateHover;
}
//判斷事件類型--QEvent::Leave
if (event->type() == QEvent::Leave)
{
eState = eBtnStateDefault;
}
//判斷事件類型--QEvent::MouseButtonPress
if (event->type() == QEvent::MouseButtonPress && ((QMouseEvent*)(event))->button()== Qt::LeftButton)
{
eState = eBtnStatePress;
}
//判斷目標
if(m_pBtnSkin==obj || m_pBtnMenu==obj || m_pBtnMin==obj || m_pBtnMax==obj || m_pBtnClose==obj)
{
//如果狀態有效
if(eState != eBtnStateNone)
{
//根據狀態設置按鈕圖標
SetBtnIcon((QToolButton *)obj,eState);
return false;
}
}
return QWidget::eventFilter(obj,event);
}
即根據事件類型設置按鈕狀態;最后在各個按鈕的click槽函數中實現相應功能即可,如窗口關閉,最大化等。

//槽函數--slot_btnclick
void TitleBar::slot_btnclick()
{
QToolButton *pBtn = (QToolButton*)(sender());
if(pBtn==m_pBtnMin)
{
emit signal_min();
}
if(pBtn==m_pBtnMax)
{
emit signal_maxrestore();
}
if(pBtn==m_pBtnClose)
{
emit signal_close();
}
}
上述代碼實現是發送自定義信號;狀態欄部分由於很簡單就不描述了。
我把二進制文件打包供下載,希望大家提出意見,有什么不對的地方望指教;下載地址為:文件下載