引言
上一篇中講述了工具箱的添加。通過一個水平布局管理器,我們將一系列的工具按鈕組合到了一起,完成了工具箱的編寫。本文在前面的基礎上實現窗體分割效果、堆棧式窗口以及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)。通過這三個功能,一個窗口能同時展示多項內容,並按邏輯功能分類。