1. 程式人生 > >C++框架_之Qt的窗口部件系統的詳解-上

C++框架_之Qt的窗口部件系統的詳解-上

同時 窗體 包含 鼠標 對象模型 種類 動態分配內存 對話框 狀態

C++框架_之Qt的窗口部件系統的詳解-上

第一部分概述

第一次建立helloworld程序時,曾看到Qt Creator提供的默認基類只有QMainWindow、QWidget和QDialog三種。是的,這三種窗體也是以後用的最多的,QMainWindow是帶有菜單欄和工具欄的主窗口類,QDialog是各種對話框的基類,而它們二者全部繼承自QWidget。不僅如此,其實所有的窗口部件都繼承自QWidget。

技術分享圖片

第二部分--核心部分

2.1Qt窗口坐標體系

坐標體系

以左上角為原點,X向右增加,Y向下增加。

技術分享圖片

對於嵌套窗口,其坐標是相對於父窗口來說的。

對於窗口的大小和位置,根據是否包含邊框和標題欄兩種情況,要用不同的函數來獲取它們的數值。

技術分享圖片

這裏的函數分為兩類,一類是包含框架的,一類是不包含框架的:

   包含框架:x()、y()、frameGeometry()、pos()和move()等函數;

  不包含框架:geometry()、width()、height()、rect()和size()等函數。

2.2QWidget

所有窗口及窗口控件都是從QWidget直接或間接派生出來的。

QWidget類是所有用戶界面對象的基類,被稱為基礎窗口部件。QWidget繼承自QObject類和QPaintDevice類,其中QObject類是所有支持Qt對象模型(Qt Object Model)的Qt對象的的基類,QPaintDevice類是所有可以繪制的對象的基類。

2.21對象模型

技術分享圖片

在Qt中創建對象的時候會提供一個Parent對象指針,下面來解釋這個parent到底是幹什麽的。

● QObject是以對象樹的形式組織起來的。

   ★當你創建一個QObject對象時,會看到QObject的構造函數接收一個QObject指針作為參數,這個參數就是 parent,也就是父對象指針。

  這相當於,在創建QObject對象時,可以提供一個其父對象,我們創建的這個QObject對象會自動添加到其父對象的children()列表。

  ★當父對象析構的時候,這個列表中的所有對象也會被析構。(註意,這裏的父對象並不是繼承意義上的父類!)--這就是為什麽Qt動態分配內存會自動清除空間

  這種機制在 GUI 程序設計中相當有用。例如,一個按鈕有一個QShortcut(快捷鍵)對象作為其子對象。當我們刪除按鈕的時候,這個快捷鍵理應被刪除。這是合理的。

●QWidget是能夠在屏幕上顯示的一切組件的父類。

  QWidget繼承自QObject,因此也繼承了這種對象樹關系。一個孩子自動地成為父組件的一個子組件。因此,它會顯示在父組件的坐標系統中,被父組件的邊界剪裁。例如,當用戶關閉一個對話框的時候,應用程序將其刪除,那麽,我們希望屬於這個對話框的按鈕、圖標等應該一起被刪除。事實就是如此,因為這些都是對話框的子組件。

  ★ 當然,我們也可以自己刪除子對象,它們會自動從其父對象列表中刪除比如,當我們刪除了一個工具欄時,其所在的主窗口會自動將該工具欄從其子對象列表中刪除,並且自動調整屏幕顯示。

  Qt 引入對象樹的概念,在一定程度上解決了內存問題。

●當一個QObject對象在堆上創建的時候,Qt 會同時為其創建一個對象樹。不過,對象樹中對象的順序是沒有定義的。這意味著,銷毀這些對象的順序也是未定義的。

●任何對象樹中的 QObject對象 delete 的時候,如果這個對象有 parent,則自動將其從 parent 的children()列表中刪除;如果有孩子,則自動 delete 每一個孩子。Qt 保證沒有QObject會被 delete 兩次,這是由析構順序決定的。

如果QObject在棧上創建,Qt 保持同樣的行為。正常情況下,這也不會發生什麽問題。來看下下面的代碼片段:

{ QWidget window; QPushButton quit("Quit", &window); }

作為父組件的 window 和作為子組件的 quit 都是QObject的子類(事實上,它們都是QWidget的子類,而QWidget是QObject的子類)。這段代碼是正確的,quit 的析構函數不會被調用兩次,因為標準 C++要求,局部對象的析構順序應該按照其創建順序的相反過程。因此,這段代碼在超出作用域時,會先調用 quit 的析構函數,將其從父對象 window 的子對象列表中刪除,然後才會再調用 window 的析構函數。

但是,如果我們使用下面的代碼:

{

    QPushButton quit("Quit");

    QWidget window;

    quit.setParent(&window);

}

情況又有所不同,析構順序就有了問題。我們看到,在上面的代碼中,作為父對象的 window 會首先被析構,因為它是最後一個創建的對象。在析構過程中,它會調用子對象列表中每一個對象的析構函數,也就是說, quit 此時就被析構了。然後,代碼繼續執行,在 window 析構之後,quit 也會被析構,因為 quit 也是一個局部變量,在超出作用域的時候當然也需要析構。但是,這時候已經是第二次調用 quit 的析構函數了,C++ 不允許調用兩次析構函數,因此,程序崩潰了。

由此我們看到,Qt 的對象樹機制雖然幫助我們在一定程度上解決了內存問題,但是也引入了一些值得註意的事情。這些細節在今後的開發過程中很可能時不時跳出來煩擾一下,所以,我們最好從開始就養成良好習慣,在 Qt 中,盡量在構造的時候就指定 parent 對象,並且大膽在堆上創建。

2.22窗口、子部件以及窗口類型

來看一個代碼片段:

    // 新建QWidget類對象,默認parent參數是0,所以它是個窗口
    QWidget *widget = new QWidget();
    // 設置窗口標題
    widget->setWindowTitle(QObject::tr("我是widget")); 
    // 新建QLabel對象,默認parent參數是0,所以它是個窗口
    QLabel *label = new QLabel();
    label->setWindowTitle(QObject::tr("我是label"));
    // 設置要顯示的信息
    label->setText(QObject::tr("label:我是個窗口"));   
    // 改變部件大小,以便能顯示出完整的內容
    label->resize(180, 20);  
    // label2指定了父窗口為widget,所以不是窗口
    QLabel *label2 = new QLabel(widget);
    label2->setText(QObject::tr("label2:我不是獨立窗口,只是widget的子部件"));
    label2->resize(250, 20);
    // 在屏幕上顯示出來
    label->show();       
    widget->show();

★在程序中定義了一個QWidget類對象的指針widget和兩個QLabel對象指針label與label2,其中label沒有父窗口,而label2在widget中,widget是其父窗口。

★窗口部件(Widget)這裏簡稱部件,是Qt中建立用戶界面的主要元素。像主窗口、對話框、標簽、還有以後要介紹到的按鈕、文本輸入框等都是窗口部件。

★在Qt中,把沒有嵌入到其他部件中的部件稱為窗口,一般的,窗口都有邊框和標題欄,就像程序中的widget和label一樣。

★QMainWindow和大量的QDialog子類是最一般的窗口類型。窗口就是沒有父部件的部件,所以又稱為頂級部件(top-level widget)。與其相對的是非窗口部件,又稱為子部件(child widget)。在Qt中大部分部件被用作子部件,它們嵌入在別的窗口中,例如程序中的label2。

2.23窗口類型

QWidget的構造函數有兩個參數:

QWidget * parent = 0和Qt::WindowFlags f = 0

前面的parent就是指父窗口部件,默認值為0,表明沒有父窗口; 而後面的f參數是Qt::WindowFlags類型的,它是一個枚舉類型,分為窗口類型(WindowType)和窗口標誌(WindowFlags。前者可以定義窗口的類型,比如我們這裏f=0,表明使用了Qt::Widget一項,這是QWidget的默認類型,這種類型的部件如果有父窗口,那麽它就是子部件,否則就是獨立的窗口。

例如:使用其中的Qt::Dialog和Qt::SplashScreen,更改程序中的新建對象的那兩行代碼:

QWidget *widget = new QWidget(0, Qt::Dialog); QLabel *label = new QLabel(0, Qt::SplashScreen);

當更改窗口類型後,窗口的樣式發生了改變,一個是對話框類型,一個是歡迎窗口類型。 而對於窗口標誌,它主要的作用是更改窗口的標題欄和邊框,而且它們可以和窗口類型進行位或操作。

下面再次更改那兩行代碼:

QWidget *widget = new QWidget(0, Qt::Dialog | Qt::FramelessWindowHint); QLabel *label = new QLabel(0, Qt::SplashScreen | Qt::WindowStaysOnTopHint);

Qt::FramelessWindowHint用來產生一個沒有邊框的窗口,而Qt::WindowStaysOnTopHint用來使該窗口停留在所有其它窗口上面。

2.3程序調試

//下面在講解窗口幾何布局的幾個函數的同時,講解一下程序調試方面的內容。 
//將主函數內容更改如下:
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget widget;
    int x = widget.x();
    int y = widget.y();
    QRect geometry = widget.geometry();
    QRect frame = widget.frameGeometry();
    return a.exec();
}

說明:

x()、y()分別返回部件的位置坐標的x、y值,它們的默認值為0。 而geometry()和frameGeometry()函數分別返回沒有邊框和包含邊框的窗口框架矩形的值,其返回值是QRect類型的,就是一個矩形,它的形式是(位置坐標,大小信息),也就是(x,y,寬,高)。

下面在int x = widget.x(); 一行代碼的標號前面點擊鼠標左鍵來設置斷點。 所謂斷點,就是程序運行到該行代碼時會暫停下來,從而可以查看一些信息,如變量值等。要取消斷點,只要在那個斷點上再點擊一下就可以了。設置好斷點後便可以按下F5或者左下角的調試按鈕開始調試。這時程序會先進行構建再進入調試模式,這個過程可能需要一些時間。在程序構建時可能會出現警告,那是因為我們定義了變量卻沒有使用造成的,不用管它。

調試模式

技術分享圖片

下面對調試模式的幾個按鈕和窗口進行簡單介紹:

①繼續按鈕。程序在斷點處停了下來,按下繼續按鈕後,程序便會像正常運行一樣,執行後面的代碼,直到遇到下一個斷點,或者程序結束。

②停止調試按鈕。按下該按鈕後結束調試。

③單步跳過按鈕。直接執行本行代碼,然後指向下一行代碼。

④單步進入按鈕。進入調用的函數內部。

⑤單步跳出按鈕。當進入函數內部時,跳出該函數,一般與單步進入配合使用。

⑥重新啟動調試會話。

⑦顯示源碼對應的匯編指令,並可以單步調試。

⑧堆棧視圖。這裏顯示了從程序開始到斷點處,所有嵌套調用的函數所在的源文件名和行號。

⑨其它視圖。這裏可以選擇多種視圖。

單步調試

  點擊一下“單步進入”按鈕,或者按下F11,這時,程序會跳轉到QWidget類的x()函數的源碼處,這裏對這個函數不做過多講解,下面直接按下“單步跳出”按鈕回到原來的斷點處。然後便開始一直按“單步跳過”按鈕,單步執行程序,並查看局部變量和監視器視圖中相應變量值的變化情況。等執行到最後一行代碼return a.exec();時,按下“停止調試”按鈕,結束調試。

  這裏要補充說明一下,我們在程序調試過程中可以進入到Qt類的源碼中,其實還有一個很簡單的方法也可以實現這個功能,就是在編輯器中將鼠標光標定位到一個類名或者函數上,然後按下F2鍵,或者點擊鼠標右鍵,選擇“跟蹤光標位置的符號”,這時編輯器就會跳轉到其源碼處。

  從變量監視器中可以看到x、y、geometry和frame四個變量初始值都是一個隨機未知數。等到調試完成後,x、y的值均為0,這是它們的默認值。而geometry的值為640x480+0+0,frame的值為639x479+0+0。

現在對這些值還不是很清楚,不過,為什麽x、y的值會是0呢?我們可能會想到,應該是窗口沒有顯示的原因,那麽就更改代碼,讓窗口先顯示出來,再看這些值。在QWidget widget;一行代碼後添加一行代碼:

widget.show();

  現在再次調試程序,這時會發現窗口只顯示了一個標題欄,先不管它,繼續在Qt Creator中點擊“單步跳過”按鈕。當我們將程序運行到最後一行代碼return a.exec();時,再次按下“單步跳過”按鈕後,程序窗口終於顯示出來了。這是因為只有程序進入主事件循環後才能接收事件,而show()函數會觸發顯示事件,所以只有在完成a.exe()函數調用進入消息循環後才能正常顯示。這次看到幾個變量的值都有了變化,但是這時還是不清楚這些值的含義。

註意:因為使用調試器進行調試要等待一段時間,而且步驟很麻煩,對於初學者來說,如果按錯了按鈕,還很容易出錯。 所以,並不推薦初學者使用。

2.4QMainWindow

QMainWindow是一個為用戶提供主窗口程序的類,包含一個菜單欄(menu bar)、多個工具欄(tool bars)、多個錨接部件(dock widgets)、一個狀態欄(status bar)及一個中心部件(central widget),是許多應用程序的基礎,如文本編輯器,圖片編輯器等。

技術分享圖片

2.41菜單欄

一個主窗口最多只有一個菜單欄。位於主窗口頂部、主窗口標題欄下面。

●創建菜單欄,通過QMainWindow類menubar()函數獲取主窗口菜單欄指針

QMenuBar * menuBar() const

●創建菜單,調用QMenu的成員函數addMenu來添加菜單

QAction* addMenu(QMenu * menu)

QMenu* addMenu(const QString & title)

QMenu* addMenu(const QIcon & icon, const QString & title)

●創建菜單項,調用QMenu的成員函數addAction來添加菜單項

QAction* activeAction() const

QAction* addAction(const QString & text)

QAction* addAction(const QIcon & icon, const QString & text)

QAction* addAction(const QString & text, const QObject * receiver,

 const char * member, const QKeySequence & shortcut = 0)

QAction* addAction(const QIcon & icon, const QString & text,

const QObject * receiver, const char * member,

const QKeySequence & shortcut = 0)

Qt 並沒有專門的菜單項類,只是使用一個QAction類,抽象出公共的動作。當我們把QAction對象添加到菜單,就顯示成一個菜單項,添加到工具欄,就顯示成一個工具按鈕。用戶可以通過點擊菜單項、點擊工具欄按鈕、點擊快捷鍵來激活這個動作。

2.42 工具欄

主窗口的工具欄上可以有多個工具條,通常采用一個菜單對應一個工具條的的方式,也可根據需要進行工具條的劃分。

●直接調用QMainWindow類的addToolBar()函數獲取主窗口的工具條對象,每增加一個工具條都需要調用一次該函數。

● 插入屬於工具條的動作,即在工具條上添加操作。

通過QToolBar類的addAction函數添加。

  ● 工具條是一個可移動的窗口,它的停靠區域由QToolBar的allowAreas決定,包括:

  ● Qt::LeftToolBarArea 停靠在左側

  ●Qt::RightToolBarArea 停靠在右側

  ● Qt::TopToolBarArea 停靠在頂部

  ●Qt::BottomToolBarArea 停靠在底部

  ● Qt::AllToolBarAreas 以上四個位置都可停靠

使用setAllowedAreas()函數指定停靠區域: 

setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea)

使用setMoveable()函數設定工具欄的可移動性

setMoveable(false//工具條不可移動, 只能停靠在初始化的位置上

2.43狀態欄

●派生自QWidget類,使用方法與QWidget類似,QStatusBar類常用成員函數:

//添加小部件

void addWidget(QWidget * widget, int stretch = 0)

//插入小部件

int insertWidget(int index, QWidget * widget, int stretch = 0)

//刪除小部件

void removeWidget(QWidget * widget)

2.5資源文件

  Qt 資源系統是一個跨平臺的資源機制,用於將程序運行時所需要的資源以二進制的形式存儲於可執行文件內部。如果你的程序需要加載特定的資源(圖標、文本翻譯等),那麽,將其放置在資源文件中,就再也不需要擔心這些文件的丟失。也就是說,如果你將資源以資源文件形式存儲,它是會編譯到可執行文件內部。

  使用 Qt Creator 可以很方便地創建資源文件。我們可以在工程上點右鍵,選擇“添加新文件…”,可以在 Qt 分類下找到“Qt 資源文件”:

技術分享圖片

點擊“選擇…”按鈕,打開“新建 Qt 資源文件”對話框。在這裏我們輸入資源文件的名字和路徑:

技術分享圖片

點擊下一步,選擇所需要的版本控制系統,然後直接選擇完成。我們可以在 Qt Creator 的左側文件列表中看到“資源文件”一項,也就是我們新創建的資源文件:

技術分享圖片

右側的編輯區有個“添加”,我們首先需要添加前綴,比如我們將前綴取名為 images。然後選中這個前綴,繼續點擊添加文件,可以找到我們所需添加的文件。這裏,我們選擇 document-open.png 文件。當我們完成操作之後,Qt Creator 應該是這樣子的:

技術分享圖片

接下來,我們還可以添加另外的前綴或者另外的文件。這取決於你的需要。當我們添加完成之後,我們可以像前面一章講解的那樣,通過使用 : 開頭的路徑來找到這個文件。比如,我們的前綴是 /images,文件是 document-open.png,那麽就可以使用:/images/document-open.png找到這個文件。

這麽做帶來的一個問題是,如果以後我們要更改文件名,比如將 docuemnt-open.png 改成 docopen.png,那麽,所有使用了這個名字的路徑都需要修改。所以,更好的辦法是,我們給這個文件去一個“別名”,以後就以這個別名來引用這個文件。具體做法是,選中這個文件,添加別名信息:

技術分享圖片

這樣,我們可以直接使用:/images/doc-open引用到這個資源,無需關心圖片的真實文件名。

如果我們使用文本編輯器打開 res.qrc 文件,就會看到一下內容:

<RCC>
        <qresource prefix="/images">
            <file alias="doc-open">document-open.png</file>
        </qresource>
        <qresource prefix="/images/fr" lang="fr">
            <file alias="doc-open">document-open-fr.png</file>
        </qresource>
</RCC>

我們可以對比一下,看看 Qt Creator 幫我們生成的是怎樣的 qrc 文件。當我們編譯工程之後,我們可以在構建目錄中找到 qrc_res.cpp 文件,這就是 Qt 將我們的資源編譯成了 C++ 代碼。

可能有中部分 下部分 。。。。

C++框架_之Qt的窗口部件系統的詳解-上