引言
在上一篇中講述了主窗體的創建和設計。主窗體的無邊框效果、陰影效果、拖動事件處理、窗體美化等工作在前面的博客中早就涉及,因此上篇博文中並未花費過多筆墨。這一篇繼續講述工具箱(Tool Button)的實現。另外,在實現的過程中還做了另外一個貼心的小功能:可伸縮的側邊欄。不過后來發現應用起來后效果不佳,於是就沒在主窗體中加入這個功能了,單獨做了一個demo作為示范。
工具箱的實現
工具箱是將若干的工具按鈕組織在一起,為用戶提供簡便導航功能的一個組件。在Qt中實現這個功能不難,Qt庫本身就提供了QToolButton和QToolBox兩個類用於類似功能。在這里我們從QToolButton類派生一個子類自定義按鈕動作。QToolButton類本身只提供了一些基本功能。因此我們需要實現一些事件處理器來自定義工具按鈕的動作和外觀。
看碼說話:
CustomToolButton::CustomToolButton(const QString& path, QWidget *parent)
: QToolButton(parent), m_filePath(path)
{
// Get the widget's palette, we do have to change the color of the tool button.
QPalette text_palette = palette();
text_palette.setColor(QPalette::ButtonText, QColor(230, 230, 230));
setPalette(text_palette);
// set the style of QToolButton.
setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
// set the font style of tool buttons
// since the return value has the type of const, we have to remove the
// constness if we want to modify.
QFont text_font = const_cast<QFont&>(font());
text_font.setBold(true);
setFont(text_font);
// set the fixed size for tool buttons.
QPixmap background(m_filePath);
setIcon(background);
setIconSize(background.size());
setFixedSize(background.width()+25, background.height()+25);
setAutoRaise(true);
m_mousePressed = false;
m_mouseHover = false;
}
/*
* Arguments topColor, centerColor, bottomColor are alpha values for QColor.
*/
void CustomToolButton::doPaintStuff(int topColor, int centerColor, int bottomColor)
{
QPainter painter(this);
QPen p(Qt::NoBrush, 1);
painter.setPen(p);
// create linear gradient brush to draw the widget
QLinearGradient linear(rect().topLeft(), rect().bottomLeft());
linear.setColorAt(0, QColor(230, 230, 230, topColor));
linear.setColorAt(0.5, QColor(230, 230, 230, centerColor));
linear.setColorAt(1, QColor(230, 230, 230, bottomColor));
// paint the widget.
painter.setBrush(linear);
painter.drawRect(rect());
}
void CustomToolButton::setButtonPressed(bool isPressed)
{
m_mousePressed = isPressed;
update();
}
void CustomToolButton::enterEvent(QEvent *)
{
m_mouseHover = true;
update();
}
void CustomToolButton::leaveEvent(QEvent *)
{
m_mouseHover = false;
update();
}
void CustomToolButton::paintEvent(QPaintEvent *event)
{
if (m_mouseHover)
{
doPaintStuff(0, 100, 150);
}
else
{
if (m_mousePressed)
{
doPaintStuff(0, 100, 150);
}
}
QToolButton::paintEvent(event);
}
void CustomToolButton::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
emit clicked();
}
}
在該子類中我們重寫了enterEvent(),leaveEvent(), paintEvent(), mousePressEvent()這幾個事件處理函數。分別對應鼠標進入、離開、點擊按鈕區域事件,paintEvent()則用於繪制按鈕的外觀。此外,還是用了幾個狀態變量,用於記錄鼠標當前的移動狀態。利用這些狀態,我們就能順利實現不同狀態的外觀繪制。值得注意的是doPaintStuff()這個函數。這個函數實際做的工作是給工具按鈕添加垂直的漸變效果。使用了QLinearGradient這個類,可以實現線性的漸變效果,這在很多界面元素設計中都非常有用。
在主函數中怎么調用這個自定義的按鈕類呢?
MainWin::MainWin(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
setWindowTitle("Tool Button");
QStringList string_list;
string_list<<":/toolWidget/tiJian"<<":/toolWidget/muMa"<<":/toolWidget/repair"<<":/toolWidget/qingLi"
<<":/toolWidget/jiaSu"<<":/toolWidget/expert"<<":/toolWidget/menZhen"<<":/toolWidget/gongNeng";
QVBoxLayout *layout = new QVBoxLayout(this);
QHBoxLayout *button_layout = new QHBoxLayout(this);
QSignalMapper *signal_mapper = new QSignalMapper(this);
for(int i=0; i< string_list.size(); i++)
{
CustomToolButton *tool_button = new CustomToolButton(string_list.at(i));
tool_button->setText("Test");
button_list.append(tool_button);
connect(tool_button, SIGNAL(clicked()), signal_mapper, SLOT(map()));
signal_mapper->setMapping(tool_button, QString::number(i, 10));
button_layout->addWidget(tool_button, 0, Qt::AlignBottom);
}
layout->addLayout(button_layout);
layout->addStretch();
setLayout(layout);
}
從代碼中看,我們用了一個循環生成了若干個自定義按鈕,然后全部放到水平布局管理器中進行管理。這個很容易理解,重點內容是QSignalMapper類的應用。QSignalMapper類是一個工具類,它主要的功能是將一組無參數信號集中管理,將信號用整型值或字符串值表示,然后再以一種統一的形式發送出去。其好處是,當有很多的信號需要統一管理的時候非常方便,不用手動調用connect()為信號綁定槽函數,因此代碼結構也更為簡練。在上面的代碼中,我們將按鈕點擊信號轉換為數值形式表示。這樣也是很自然的做法,一方面形式簡單,另一方面水平排列的工具按鈕按序編號符合人類習慣。

可伸縮的側邊欄
還是看看什么叫做可伸縮的側邊欄,這樣的功能在QQ的聊天窗口就可以看見:


側邊欄的收縮可以在需要的時候隱藏部分組件,從而為其他組件提供更為廣闊的視角。如上圖中的側邊欄收縮為文本框組件提供更多的空間,整個界面上看起來也更為清爽。稍微一剖析:這個邊欄要能點擊,點擊之后要切換圖標,響應的組件要隱藏。如此一分析,代碼可如下編寫:
TestSideBar::TestSideBar(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
flag = false;
mainLayout = new QHBoxLayout(this);
mainSplitter = new QSplitter(Qt::Horizontal, this);
mainSplitter->setFrameStyle(QFrame::NoFrame);
mainSplitter->setHandleWidth(1);
mainSplitter->setChildrenCollapsible(false);
zoomButton = new QPushButton(this);
zoomButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
zoomButton->setIcon(QIcon(":/right"));
zoomButton->setFixedWidth(6);
zoomButton->setFocusPolicy(Qt::NoFocus);
zoomButton->setStyleSheet("background: #E8E8E8; border: none; padding: 0px;");
leftWidget = new QWidget(this);
leftWidget->setStyleSheet("background: yellow;");
rightWidget = new QWidget(this);
rightWidget->setStyleSheet("background: blue;");
mainSplitter->addWidget(leftWidget);
mainSplitter->addWidget(rightWidget);
mainLayout->addWidget(zoomButton);
mainLayout->addWidget(mainSplitter);
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
QWidget* w = new QWidget(this);
w->setLayout(mainLayout);
setCentralWidget(w);
connect(zoomButton, SIGNAL(clicked()), this, SLOT(onZoomClicked()));
}
void TestSideBar::onZoomClicked()
{
if (flag) // 根據當前的展開狀態,進行切換
{
flag = false;
zoomButton->setIcon(QIcon(":/left"));
leftWidget->setVisible(true);
}
else
{
flag = true;
zoomButton->setIcon(QIcon(":/right"));
leftWidget->setVisible(false);
}
}


可以發現這里的側邊欄果然一直固定在最左側,要達到QQ聊天界面那要的效果呢,只需要改幾行代碼就好了:
TestSideBar::TestSideBar(QWidget *parent)
: QMainWindow(parent)
{
// 其他保持不變,省略……
mainSplitter->addWidget(leftWidget);
mainSplitter->addWidget(zoomButton);
mainSplitter->addWidget(rightWidget);
mainLayout->addWidget(mainSplitter, 1);
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
// 其他保持不變,省略……
}


小結
這一篇主要講了上篇遺留的一個功能,工具按鈕組的開發。另外,實現了另外一個功能:側邊欄的伸縮。下一篇繼續樹形控件(tree widget)、堆棧式窗口布局(stacked layout)的講解。
