引言
上一篇中講述了工具箱的添加。通過一個水平布局管理器,我們將一系列的工具按鈕組合到了一起,完成了工具箱的編寫。本文在前面的基礎上實現窗體分割效果、堆棧式窗口以及Tab選項卡。
窗體分割
窗體分割是一個常見的功能,尤其在一些IDE中用的非常廣泛。主要是窗體分割能夠在視覺上對程序功能進行分組分類,在保證界面美觀的同時還能保證內容井井有條,何樂而不為呢?Qt中提供了一個用於分割窗體的類:QSplitter。這個類的使用也非常簡單,准備好需要分割的窗口,設置好分割方向和比例即可。不過值得注意的是,QSplitter是一個窗口管理類,在沒有添加子控件是看不到QSplitter效果的。這一點在Qt Designer中也可以驗證。
在我們的項目中,我們增加一個QSplitter類成員,並在主窗口的構造函數中添加如下代碼:
splitter = new QSplitter(Qt::Horizontal, this); splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); splitter->setHandleWidth(1); splitter->addWidget(new QWidget(this)); splitter->addWidget(new QWidget(this)); splitter->handle(1)->setDisabled(true); splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 3);
在上面的代碼中,我們將左右兩個子窗口的比例設置為1:3。也就是說左邊窗口占25%的空間,右邊占75%。另外,我們還設置了QSplitter的Handle寬度。handle指的就用於分割窗體的那根線。我們將其寬度設置為1個像素寬,setDisabled(true)將其設置為不可拖動的。這樣一來,用戶就無法用鼠標拖拽左右窗口的大小了。看看效果:

在分割出來的子窗口中,還可以進行進一步的分割,也就是QSplitter的嵌套使用。
堆棧式窗口及Tab選項卡
堆棧式窗口取義於數據結構中的堆棧,也就是說多個窗口堆疊在一起,當用戶點擊對應層的窗口時進行切換。以騰訊QQ的設置窗口為例,看看到底是怎樣一種效果:


當用戶點擊“基本設置”時,窗口中的內容全部都是相關的選項卡;當點擊“安全設置”的時候,窗口內容切換為對應的選項卡內容。也就是說一個窗口被另一個窗口“遮住”了。利用這種形式可以很容易的組織邏輯相關的內容。QStackedWidget是Qt為我們提供的一個實現這種功能的類。除此之外,Qt還提供了一個堆棧式窗口布局管理器類:QStackedLayout。而事實上,QStackedWidget的功能正是基於QStackedLayout實現的。那么,我們又該如何去組織這樣一種結構呢?
基本思路其實也很簡單。QStackedWidget繼承自QWidget,它本身是一個控件容器,但是也可以作為子控件放置於其他的容器中去。那么,我們先構造好一個QStackedWidget,然后再考慮集成到父窗口中去:
TrojanAssessment::TrojanAssessment(QWidget *parent)
: ShadowWindow(parent)
{
// 前面省略……
// create tree widget and stacked widget
treeWidget = new QTreeWidget(this);
treeWidget->setFrameShape(QFrame::NoFrame);
stackedWidget = new QStackedWidget(this);
stackedWidget->resize(680, 500);
stackedWidget->setFrameShape(QFrame::NoFrame);
initStackedWidget();
initTreeWidget();
splitter = new QSplitter(Qt::Horizontal, this);
splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
splitter->setHandleWidth(1);
splitter->addWidget(treeWidget);
splitter->addWidget(stackedWidget);
splitter->handle(1)->setDisabled(true);
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 3);
// create title widget and status bar
titleWidget = new TitleWidget(this);
icon_label = new QLabel(this);
icon_label->setPixmap(QPixmap(":/menu/cloud"));
icon_label->setFixedSize(QPixmap(":/menu/cloud").size());
lastrun_label = new QLabel(this);
m_bottomLayout = new QHBoxLayout(this);
m_bottomLayout->addStretch();
m_bottomLayout->addWidget(icon_label, 0, Qt::AlignCenter);
m_bottomLayout->addWidget(lastrun_label, 0, Qt::AlignCenter);
m_bottomLayout->setSpacing(5);
m_bottomLayout->setContentsMargins(0, 3, 10, 3);
// remember the time when the program start
login_dt = QDateTime::currentDateTime();
restoreSettings();
QPalette plt;
plt.setBrush(QPalette::Window, QBrush(Qt::white));
treeWidget->setPalette(plt);
treeWidget->setAutoFillBackground(true);
stackedWidget->setPalette(plt);
stackedWidget->setAutoFillBackground(true);
// 省略更多……
}
void TrojanAssessment::initStackedWidget()
{
/* initialize the stacked pages */
fmp = new FileMonitorPage(this);
iep = new IEPage(this);
mp = new MemoryPage(this);
np = new NetworkPage(this);
pp = new ProcessPage(this);
rp = new RegisterPage(this);
scp = new SecurityCenterPage(this);
//add page widgets to StackedWidgets
stackedWidget->addWidget(fmp);
stackedWidget->addWidget(iep);
stackedWidget->addWidget(mp);
stackedWidget->addWidget(np);
stackedWidget->addWidget(pp);
stackedWidget->addWidget(rp);
stackedWidget->addWidget(scp);
// set File Monitoring as the default page.
stackedWidget->setCurrentWidget(fmp);
connect(this, SIGNAL(changeTabFMP(int)), fmp, SLOT(onChangeTab(int)));
connect(this, SIGNAL(changeTabPP(int)), pp, SLOT(onChangeTab(int)));
connect(this, SIGNAL(changeTabMP(int)), mp, SLOT(onChangeTab(int)));
connect(this, SIGNAL(changeTabNP(int)), np, SLOT(onChangeTab(int)));
connect(this, SIGNAL(changeTabRP(int)), rp, SLOT(onChangeTab(int)));
connect(this, SIGNAL(changeTabSCP(int)), scp, SLOT(onChangeTab(int)));
connect(this, SIGNAL(changeTabIEP(int)), iep, SLOT(onChangeTab(int)));
}
在構造函數中我們構造了一個QStackedWidget實例,在initStackedWidget()中,用addWidget陸續添加了7個子控件。這里需要注意的是:我們添加的每一個控件都是堆棧式窗口中的“一頁”了,setCurrentWidget()用於設置當前可見的“頁”。那么,Tab選項卡又是如何實現的呢?繼承QTabWidget類。QTabWidget也是一個容器類,可以添加很多子控件。每一個控件都是一個Tab了。以File monitor這一頁為例:
class FileMonitorPage : public QTabWidget
{
Q_OBJECT
public:
FileMonitorPage(QWidget *parent = 0);
~FileMonitorPage(){}
private slots:
void onChangeTab(int index);
private:
FileMonitorPage& operator=(const FileMonitorPage& obj);
FileMonitorPage(const FileMonitorPage& obj);
private:
//QTabWidget* m_tabWidget;
DataFileTab* m_dataFileTab;
ExecFileTab* m_execFileTab;
FileBrowserTab* m_browserTab;
};
//////////////////////////////////////////////////////////////////////////
//Tab for data file monitoring
class DataFileTab : public QWidget
{
Q_OBJECT
public:
DataFileTab(QWidget* parent = 0);
~DataFileTab(){}
private:
DataFileTab(const DataFileTab& obj);
DataFileTab& operator=(const DataFileTab& obj);
private:
CustomItemModel* m_model;
QSortFilterProxyModel* m_proxy;
QTableView* m_view;
QHBoxLayout* m_topLayout;
QLineEdit* m_filter;
QPushButton* m_clearBtn;
QPushButton* m_exportBtn;
QHBoxLayout* m_statusLayout;
QLabel* m_status;
QLineEdit* m_status_info;
QPushButton* m_chooseDir;
QPushButton* m_startBtn;
QPushButton* m_stopBtn;
QVBoxLayout* m_mainLayout;
};
//////////////////////////////////////////////////////////////////////////
// Tab for executable file monitoring
class ExecFileTab : public QWidget
{
Q_OBJECT
public:
ExecFileTab(QWidget* parent = 0);
~ExecFileTab(){}
private:
ExecFileTab(const ExecFileTab& obj);
ExecFileTab& operator=(const ExecFileTab& obj);
private:
QTableView* m_view;
CustomItemModel* m_model;
QHBoxLayout* m_topLayout;
QPushButton* m_clearBtn;
QPushButton* m_startBtn;
QPushButton* m_stopBtn;
QVBoxLayout* m_mainLayout;
};
//////////////////////////////////////////////////////////////////////////
// Tab for file browser file monitoring
class FileBrowserTab : public QWidget
{
Q_OBJECT
public:
FileBrowserTab(QWidget* parent = 0);
~FileBrowserTab(){}
private:
FileBrowserTab(const FileBrowserTab& obj);
FileBrowserTab& operator=(const FileBrowserTab& obj);
private:
QTreeView* m_view;
QFileSystemModel* m_model;
QVBoxLayout* m_layout;
};
在File Monitor中我們添加了三個TAB:DataFileTab,ExecFileTab,FileBrowserTab,這三個類每一個都有自己的布局管理器和子控件。這么說來,QTabWidget和QStackedWidget的結構是非常相似的。其實,編寫Qt程序的時候,我們要組合一個窗口其實是非常簡單的。QWidget可以通過布局管理器嵌套任意多的子窗口,從而構建負責的UI元素。最終的效果看起來是這樣的:

小結
本文重點實現了三個功能:窗體分割(QSplitter),堆棧式窗口(QStackedWidget),Tab選項卡(QTabWidget)。通過這三個功能,一個窗口能同時展示多項內容,並按邏輯功能分類。
