Qt圖形用戶界面編程技術入門
本文向介紹利用Qt開發圖形用戶界面的應用程序的入門知識。這里,我們首先介紹了如何搭建Qt的開發環境,之后通過一些簡單的示例程序來循序漸進地介紹Qt的“信號和槽”以及布局等基本概念。我們希望以此來幫助讀者盡快地對Qt圖形用戶界面應用程序開發有一個初步的認識,并為進一步學習打下一個良好的基礎。
一、什么是Qt
Qt 是一個用于桌面系統和嵌入式開發的跨平臺應用程序框架。它包括一個直觀的API和一個豐富的類庫,以及用于GUI開發和國際化的集成工具,另外它支持Java™和C++開發。利用它,我們無須重新編寫源代碼,便可以構建運行在不同桌面操作系統和嵌入式設備上的軟件應用程序。
借助Qt,我們可以更快速地構建先進的用戶界面:它不僅提供了豐富的標準widgets庫,動態布局引擎等GUI功能,還通過集成OpenGL® 與OpenGL ES提供了先進的3D可視化支持,此外,它還具有強大的圖形畫布和Widgets樣式表,使我們得以使用變焦、旋轉和人機互動功能構建先進的用戶界面,并且能用寥寥幾行代碼便可快速定制自己的用戶界面。
雖然Qt提供了許多高級功能,但千里之行,始于足下,還是讓我們先從最基本的知識開始入手吧。下面介紹如何搭建Qt開發環境。
二、搭建Qt開發環境
雖然Qt自身帶有構建工具,但它是在命令行下使用的,多少有些不便。所以,我們在此自己動手建設自己的Qt集成開發環境。下面介紹Qt開發環境的具體搭建過程。
首先,從互聯網上搜索并下載Dev-C++,安裝很簡單,一路回車就可以了。然后,到http://www.trolltech.com/download/下載***的安裝包,對于Windows系統來說,可以下載已編譯好的安裝包,當前***版為qt-win-opensource-4.4.0-mingw.exe。在Qt安裝過程中唯一需要注意的是,當安裝程序要求選擇mingw的路徑時,直接選擇Dev-Cpp的安裝路徑就行了。安裝好上述兩個軟件后,***還要到http://download.csdn.net/source/219376下載Qt4 For Dev-Cpp Templates,下載后將其解壓到Dev-Cpp的Templates文件夾下即可。這是用于在Dev-Cpp下開發Qt程序的模板資源。
***,把Qt安裝目錄中的\bin目錄中的動態鏈接庫拷貝到windows目錄下,這樣當運行編譯好的Qt程序時,就再也不會碰到無法找到Qt的DLL 的問題了。
三、我們的***個Qt程序
迄今為止,我們已經搭建好了Qt的開發環境,接下來就可以編寫我們的***個Qt程序。按照學習編程的老傳統,一般編寫的***個程序都是一個Hello程序,我們也不例外。
運行Dev-C++,在其“文件”菜單中選擇“新建”菜單項,然后單擊“工程”命令,出現如下圖所示的對話框:
圖1 新建Qt工程
選擇其中的“Empty Project”,以便建立一個空項目,將項目名稱定為“hello”,其他選擇默認,如圖1所思,然后單擊“確定”按鈕。在彈出的“Create new project”對話框中選擇工程文件名稱和保存路徑,如圖2所示。
圖2 保存項目文件
上面已經新建了一個空的Qt項目,現在為它添加一個源代碼文件。在Dev-C++的“文件”菜單中選擇“新建”菜單項,然后單擊“源代碼”命令,在彈出的確認對話框中單擊“Yes”按鈕。在編輯區中錄入如下代碼,保存源代碼文件時將其命名為hello.cpp。
- #include <QApplication>
- #include <QLabel>
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QLabel *label = new QLabel("Hello World!");
- label->show();
- return app.exec();
- }
現在,讓我們來編譯該程序。單擊“運行”菜單中的“編譯”菜單項,出現如下圖所示對話框時,說明沒有出現錯誤,編譯成功。
圖3 編譯成功
單擊“關閉”按鈕。很好,現在運行我們***個Qt應用程序的時候到了,單擊“運行”菜單中的“運行”菜單項。來,看看我們的“大作”吧!
圖4 我們的hello程序
上面演示了在集成開發環境中開發Qt應用程序的整個過程,下面開始介紹我們的源代碼。俗話說,萬事開頭難,所以我們在這里會盡可能細致地為讀者講解這些代碼。
- #include <QApplication>
- #include <QLabel>
在這個程序中,我們總共用到了兩個類QApplication和Qlabel,根據先聲明后使用的原則,我們在上面兩行將這兩個類的定義包含到我們的代碼中。對Qt來說,它的每一個類都有一個同名的頭文件與之對應,這個類的定義就在這個頭文件中。我們注意到,這兩個頭文件都是以大寫字母開頭的,實際上類對應的頭文件都是這樣。
- int main(int argc, char *argv[])
在這里,main()函數是程序的入口。在使用Qt的時候,main()一般只是執行一些初始化工作,接著就把控制轉交給Qt庫,然后Qt庫通過事件來向程序報告用戶的行為。
- QApplication app(argc, argv);
上面這行代碼為QApplication創建了一個對象,實際上,在每一個使用Qt的應用程序中都必須有一個QApplication對象,該對象用來管理應用程序的各種資源。一般說來,在使用Qt的窗口部件被之前,要首先創建QApplication對象。因為Qt支持命令行參數,所以這里的QApplication帶有argc和argv,用來接收入口函數從系統那里接收到的命令行變量,以便進一步處理。
- QLabel *label = new QLabel("Hello World!");
這一行創建了一個窗口部件QLabel,我們用它來顯示一則消息“Hello World!”。按照Qt的術語,一個窗口部件就是用戶界面中的一個可見的用戶界面對象,它能夠處理用戶輸入和繪制圖形,它相當于Windows的術語中的一個控件或容器。我們可以改變窗口部件的全部觀感、主要屬性(比如顏色等)以及窗口部件的內容等。我們常見的按鈕、菜單、滾動條和框架等都屬于窗口部件。窗口部件可以包含其它的窗口部件,比如應用程序窗口通常就是一個窗口部件,而其中又包含了QMenuBar、QToolBars、QstatusBar以及其它的窗口部件。大多數應用程序使用一個QMainWindow或者QDialog作為自己的主窗口,但是這不是必須的的,實際上任何窗口部件都能當作程序的主窗口。就本例而言,窗口部件QLabel就是應用程序的主窗口或者說是主窗口部件。如果用戶關閉了主窗口部件,應用程序就會退出。
- label->show();
默認時,窗口部件是不可見的,之所以這樣,是為了讓我們可以在顯示之前對窗口部件進行必要的設定,以防止閃爍現象的發生。上面這一行代碼的作用是使標簽變為可見的。
- return app.exec();
上面這一行代碼將應用程序的控制權交給Qt,交權后,應用程序便進入事件循環狀態。這時的程序只是靜靜地等待用戶的鼠標或鍵盤之類的動作。當用戶發出動作時,就會生成相應的事件,如果這些事件正是該程序需要響應的那些事件,它便會執行一些函數來響應用戶的動作。
迄今為止,我們已經編譯運行了***個Qt應用程序,并且對該程序的源代碼有了初步的了解,但是我們的這個程序非常簡單,簡單到顯示一條消息后就只能通過標題欄上的“關閉”按鈕來關閉。接下來我們將進一步學習如何通過窗口部件來跟用戶互動。
#p#
四、跟用戶互動
在第二個實例中,我們將為大家介紹如何響應用戶的動作。該程序也很簡單,它僅由一個按鈕組成,當用戶單擊該按鈕時,程序就會退出。這個應用程序運行畫面如下所示:
圖5 利用按鈕跟用戶進行交互
下面是該程序的源代碼:
- #include <QApplication>
- #include <QPushButton>
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QPushButton *button = new QPushButton("Quit");
- QObject::connect(button, SIGNAL(clicked()),
- &app, SLOT(quit()));
- button->show();
- return app.exec();
- }
我們看到,這里的源代碼跟上面的非常相似,只有兩處不一樣,一是主窗口部件是QPushButton,而非Qlabel;二是將用戶操作(如這里的單擊按鈕)跟一段代碼聯系在一起。當用戶執行某些操作,或狀態發生變化時,Qt的窗口部件就會發出一些信號來指示這些事件的發生。舉例來說,當用戶單擊按鈕時,QPushButton就會發出一個clicked()信號。這時,跟這個事件相聯系的代碼就會就會自動執行。在QT中,對這樣的代碼有一個專門的稱謂,叫做槽。對這里的例子來說,我們將按鈕的clicked()信號連到QApplication的槽quit()上。所以,單擊Quit按鈕,或按下空格鍵時,該程序就會終止。
這里涉及到Qt的一個基本思想,那就是“信號和槽”。這一思想需要專門一篇文章來進行解釋,我們這里只要知道,每個Qt對象,無論是直接還是間接繼承QObject對象的對象,都能用信號發出信息,也能用槽來接收信息并作出反應。這里要注意的是,所有窗口部件都是Qt對象,因為它們繼承自QWidget,而Qwidget又繼承自QObject。
這里的connect()是QObject中的一個靜態函數,它的作用是將信號和槽連接在一起。比如本例中,它把按鈕的clicked()信號和QApplication的槽quit()連接起來了,所以當這個按鈕被按下的時候,這個程序就退出了。
五、窗口部件的布局
讀者可能已經發現,我們上面的兩個例子中,都只是用了一個窗口部件,但是現實情況卻是一個程序界面中有多個窗口部件,并且一些窗口部件通常還位于其他窗口部件之內。這時問題就來了:如何將一些窗口部件放進另一個窗口部件中?放進去以后又如何對它們進行布置呢?別急,這些事情Qt的設計者早就替我們考慮到了,下面就介紹Qt的自動布局支持。
除了解釋如何使用布局來管理窗口部件在窗口中的幾何形狀之外,本示例程序還將為讀者介紹如何使用信號和槽來實現兩個窗口部件的同步。如下圖所示:
圖6 窗口部件布局與同步示例
我們可以在這個界面中輸入一個1到100之間的數字。當然,我們可以用兩種方法輸入數字,既可以拖動滑塊,也可以使用Spinbox按鈕。但是,無論使用哪一種方式,只要一邊表示的數字發生了變化,另一邊也會隨之改變,所以它們總能保持一致。該示例程序的源代碼如下所示:
- #include <QApplication>
- #include <QHBoxLayout>
- #include <QSlider>
- #include <QSpinBox>
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QWidget *window = new QWidget;
- window->setWindowTitle("Enter Your Number");
- QSpinBox *spinBox = new QSpinBox;
- QSlider *slider = new QSlider(Qt::Horizontal);
- spinBox->setRange(0, 100);
- slider->setRange(0, 100);
- QObject::connect(spinBox, SIGNAL(valueChanged(int)),
- slider, SLOT(setValue(int)));
- QObject::connect(slider, SIGNAL(valueChanged(int)),
- spinBox, SLOT(setValue(int)));
- spinBox->setValue(60);
- QHBoxLayout *layout = new QHBoxLayout;
- layout->addWidget(spinBox);
- layout->addWidget(slider);
- window->setLayout(layout);
- window->show();
- return app.exec();
- }
該應用程序的界面由三個窗口部件組成,分別是QSpinBox、QSlider和QWidget,其中QWidget是本程序的主窗口,然后在QWidget內再引用QSpinBox和QSlider,所以后兩者是前者的子部件,或者說前者是后兩者的父部件。QWidget本身沒有父部件,因為它是一個***窗口。QWidget及其子類的構造函數使用參數QWidget *來規定其父部件。
下面我們對源代碼進行解釋:
- QWidget *window = new QWidget;
- window->setWindowTitle("Enter Your Number");
上面兩行將QWidget設置為該程序的主窗口,其中setWindowTitle()用于規定顯示在這個窗口標題欄中的文本內容。
- QSpinBox *spinBox = new QSpinBox;
- QSlider *slider = new QSlider(Qt::Horizontal);
上面兩行創建了一個QSpinBox和一個QSlider,然后,
- spinBox->setRange(0, 100);
- slider->setRange(0, 100);
這兩行設置了其有效范圍,我們這里選擇0至100之間的數字。
- QObject::connect(spinBox, SIGNAL(valueChanged(int)),
- slider, SLOT(setValue(int)));
- QObject::connect(slider, SIGNAL(valueChanged(int)),
- spinBox, SLOT(setValue(int)));
在上面的兩個語句中,我們調用了QObject::connect()兩次,實現了Spinbox按鈕和滑塊之間的同步,從而使得顯示的結果將保持一致。當一個窗口部件中的值發生變化時,它就會發出valueChanged(int)信號,并用這個新值來調用另一個窗口部件的槽setValue(int),這樣它們就能保持一致。
- spinBox->setValue(60);
上面這一行代碼將Spinbox按鈕的值設為60。這時,QSpinBox會發出valueChanged(int)信號,其中參數int為60,這個參數傳遞給QSlider的槽setValue(int),這個槽繼而將滑塊值設為60。 因為滑塊自己的值變了,所以它會發出信號valueChanged(int)來觸發Spinbox按鈕的槽setValue(int)。不過由于Spinbox按鈕的值早已是60,兩者是一致的,所以它就不會繼續發信號了,同步過程至此結束。
- QHBoxLayout *layout = new QHBoxLayout;
- layout->addWidget(spinBox);
- layout->addWidget(slider);
通過上面三行代碼,我們新建了一個一個布局管理器,然后將Spinbox按鈕和滑塊這兩個窗口部件交給這個布局管理器,讓它來對這兩個部件的大小和位置等作出安排。一個布局管理器是一個對象,用于設置窗口部件的位置和尺寸。Qt的布局管理器分為三大類:
QHBoxLayout將窗口小部件從左至右,或者從右到左水平放置窗口部件。
QVBoxLayout將窗口小部件自上而下垂直布置。
QGridLayout將窗口小部件布置在一個網格中。
- window->setLayout(layout);
我們在以上代碼中調用QWidget::setLayout(),這會在窗口中安裝一個布局管理器。這樣一來,QSpinBox和QSlider又進一步成為安裝布局管理器的窗口部件的子部件,所以當我們創建一個將來要放入布局管理器的窗口部件時,不用顯式地指出其父部件。
如果我們要想顯式地給QSpinBox和Qslider指定父窗口部件的話,可以在創建它們時將參數window傳遞給QSpinBox和QSlider的構造函數,以規定讓window作為它們的父部件。
使用布局管理器有很大的優點,就像上面看到的那樣,即使不對任何窗口部件的位置和尺寸進行任何顯式地設置,***QSpinBox和QSlider還是很好地并排布置在了一起。這是因為QHBoxLayout會根據需要,自動地為其負責的窗口部件指定合適的位置和大小。更重要的是,該布局管理器將我們從在程序中硬編碼窗口部件的屏幕位置的瑣碎工作中解放了出來,它會替我們處理窗口平滑縮放等相應事項。
小結
本文簡單介紹了信號和槽的連接以及布局的基本知識,并介紹了Qt的完全面向對象的構造和利用窗口小部件的方法。我們發現,利用Qt構造用戶界面的方式簡潔而又靈活,我們只需要創建窗口部件對象,然后根據需要為其設置屬性即可。此外,我們還可以將生成的窗口部件添加到布局管理器中,這樣,布局管理器就會自動地調整這些窗口部件的尺寸和位置。同時,我們還可以通過信號和槽機制方便的將用戶界面的行為跟Qt的窗口部件聯系在一起,這樣就能實現程序跟用戶的互動了。