Qt(官方發(fā)音 [kju:t],音同 cute)是一個跨平臺的 C++ 開發(fā)庫,主要用來開發(fā)圖形用戶界面(Graphical User Interface,GUI)程序,當(dāng)然也可以開發(fā)不帶界面的命令行(Command User Interface,CUI)程序。
Qt 還存在 Python、Ruby、Perl 等腳本語言的綁定, 也就是說可以使用腳本語言開發(fā)基于 Qt 的程序。開源社區(qū)就是這樣,好東西就會被派生擴(kuò)展,到處使用, 越來越壯大。
Qt 支持的操作系統(tǒng)有很多,例如通用操作系統(tǒng) Windows、Linux、Unix,智能手機(jī)系統(tǒng) Android、iOS、WinPhone, 嵌入式系統(tǒng) QNX、VxWorks 等等。
Qt 雖然經(jīng)常被當(dāng)做一個 GUI 庫,用來開發(fā)圖形界面應(yīng)用程序,但這并不是 Qt 的全部;Qt 除了可以繪制漂亮的界面(包括控件、布局、交互),還包含很多其它功能,比如多線程、訪問數(shù)據(jù)庫、圖像處理、音頻視頻處理、網(wǎng)絡(luò)通信、文件操作等,這些 Qt 都已經(jīng)內(nèi)置了。
Qt 是應(yīng)用程序開發(fā)的一站式解決方案,有了 Qt,你就可以高枕無憂了!Qt 本身包含的模塊也日益豐富, 一直有新模塊和第三方模塊加入進(jìn)來。
大部分應(yīng)用程序都可以使用 Qt 實現(xiàn),除了與計算機(jī)底層結(jié)合特別緊密的,例如驅(qū)動開發(fā),它直接使用硬件提供的編程接口,而不能使用操作系統(tǒng)自帶的函數(shù)庫。
1997年,Qt 被用來開發(fā) Linux 桌面環(huán)境 KDE,大獲成功,使 Qt 成為 Linux 環(huán)境下開發(fā) C++ GUI 程序的事實標(biāo)準(zhǔn)。
下面的程序都使用 Qt 開發(fā):WPS、YY語音、Skype、豆瓣電臺、蝦米音樂、淘寶助理、千牛、暴雪的戰(zhàn)網(wǎng)客戶端、VirtualBox、Opera、咪咕音樂、Google地圖、Adobe Photoshop Album 等。
Linux 也是嵌入式的主力軍,廣泛應(yīng)用于消費(fèi)類電子、工業(yè)控制、軍工電子、電信/網(wǎng)絡(luò)/通訊、航空航天、汽車電子、醫(yī)療設(shè)備、儀器儀表等相關(guān)行業(yè)。
Qt 雖然也支持手機(jī)操作系統(tǒng),但是由于 Android 本身已經(jīng)有 Java 和 Kotlin,iOS 本身已經(jīng)有 Objective-C 和 Swift,所以 Qt 在移動端的市場份額幾乎可以忽略。
總起來說,Qt 主要用于桌面程序開發(fā)和嵌入式開發(fā)。
Qt 目前支持主流的 Android、iOS、WinPhone 等智能機(jī)操作系統(tǒng)。MeeGo 是基于 Qt 開發(fā)的操作系統(tǒng),由于被諾基亞拋棄了, 只剩一代絕版諾基亞 N9 手機(jī)。
諾基亞手機(jī)部門出售給微軟之后,大部分諾基亞手機(jī)系統(tǒng)開發(fā)人員都被遣散了。
原來諾基亞的一部分人成立新的 Jolla(卓藍(lán))公司,發(fā)布了 MeeGo 的衍生版智能手機(jī)系統(tǒng) Sailfish(旗魚),相應(yīng)的手機(jī)和平板也發(fā)布開賣了。
另一撥人投靠了 Tizen(泰澤),Tizen 是英特爾和三星力推的智能手機(jī)系統(tǒng),該系統(tǒng)整合了 Limo 和 MeeGo,因為 MeeGo 系統(tǒng)被諾基亞拋棄,英特爾其實是被出賣了,所以只能聯(lián)合三星重造智能手機(jī)系統(tǒng) Tizen。
目前基于 Tizen 的首款手機(jī)三星 Z1 在印度上市了。在 Tizen 陣營,國內(nèi)有中興、百度涉及了。Qt 開源項目里也有 Qt for Tizen 版本,有興趣的可以去搜搜。
Qt 公司有專門針對移動開發(fā)的商業(yè)版本,20 歐元或 25 美元一個月。不做商業(yè)可以無視這個,用開源版本也是可以開發(fā)如 Android、iOS、WinPhone 應(yīng)用的。
本教程關(guān)注的是傳統(tǒng)桌面操作系統(tǒng)開發(fā)的,移動開發(fā)可以參考 Qt 官方的文檔。
說到 Qt 的發(fā)展史,那真是一波三折,幾經(jīng)賣身。
Qt 最早是 1991 年由挪威的 Eirik Chambe-Eng 和 Haavard Nord 開發(fā)的, 他們隨后于 1994 年 3 月 4 號正式成立奇趣科技公司(Trolltech)。Qt 原本是商業(yè)授權(quán)的跨平臺開發(fā)庫, 在 2000 年奇趣科技公司為開源社區(qū)發(fā)布了遵循 GPL(GNU General Public License)許可證的開源版本。
在 2008 年,諾基亞公司收購了奇趣科技公司,并增加了 LGPL(GNU Lesser General Public License)的授權(quán)模式。諾基亞聯(lián)合英特爾利用 Qt 開發(fā)了全新的智能手機(jī)系統(tǒng) MeeGo,可惜遭遇了微軟木馬屠城,諾基亞被迫放棄了 MeeGo, 而 Qt 商業(yè)授權(quán)業(yè)務(wù)也于 2011 年 3 月出售給了芬蘭 IT 服務(wù)公司 Digia。
當(dāng)然好消息是 Digia 于 2014 年 9 月宣布成立 Qt Company 全資子公司,獨(dú)立運(yùn)營 Qt 商業(yè)授權(quán)業(yè)務(wù)。目前 Qt 公司大力推廣移動平臺開發(fā)和商業(yè)應(yīng)用, 總的來說 Qt 歷經(jīng)曲折,現(xiàn)在算是步入正軌了。
經(jīng)過 20 多年的發(fā)展,Qt 已經(jīng)成為最優(yōu)秀的跨平臺開發(fā)框架之一,在各行各業(yè)的項目開發(fā)中得到廣泛應(yīng)用。許多大型軟件都是用 Qt 開發(fā)的,如 Autodesk Maya、Google Earth、Skype、WPS Office等。
永遠(yuǎn)不要忽視微軟帝國的威脅,作為軟件業(yè)的一代霸主,任何人都不要天真地試圖和它做朋友,因為霸主不可能有朋友。微軟的木馬屠城是所有諾基亞人和芬蘭人的痛,希望讀者們都記牢這條。
之前提到 Qt 原本是商業(yè)授權(quán)軟件,是怎么開源的呢?這就涉及 Qt 和 KDE 的糾葛了。
KDE 是 Linux 操作系統(tǒng)的桌面環(huán)境,與 GNOME 桌面是類似的,作為開源桌面它們競爭的情況更為多見,有興趣的讀者請猛擊《Linux桌面環(huán)境》了解更多。
KDE 是采用 GPL 許可證發(fā)布的開源軟件,而最初 Qt 是商業(yè)授權(quán)的,存在商業(yè)侵權(quán)風(fēng)險,GNOME 則是基于開源 GTK 庫的,沒有什么商業(yè)風(fēng)險,這一度是 GNOME 優(yōu)越于 KDE 的特性。
由于 Qt 的商業(yè)授權(quán),KDE 社區(qū)一度混亂糾結(jié),與此同時 GNOME 則如火如荼發(fā)展起來了。 KDE 畢竟算是親兒子,被另一波人欺負(fù),奇趣科技公司當(dāng)然看不下去了,最后是奇趣科技公司為了贏得開發(fā)者的支持,為 Qt 增加了 GPL 的開源授權(quán), 對于開源社區(qū)而言,遵循 GPL 使用 Qt 就不需要付費(fèi),這為 KDE 解決了燃眉之急。
之后 KDE 桌面和 GNOME 都發(fā)展壯大起來,都做得越來越好了。
除了商業(yè)授權(quán),目前 Qt 的開源授權(quán)有兩種,一種是 GPL 授權(quán),另一種是 LGPL 授權(quán)(諾基亞收購后新增)。
對這兩種開源授權(quán),簡單來說,使用 GPL 版本的軟件一定還是 GPL 的開源軟件,無論是使用了 Qt 的程序代碼還是修改了 Qt 庫代碼,都必須按照 GPL 來發(fā)布,這是 GPL 的傳染性。
GPL 是什么都要開源,這對商業(yè)軟件應(yīng)用是不利的,所以諾基亞增加了 LGPL 授權(quán) (第一個 L 可以叫 Lesser 寬松版或 Library 開發(fā)庫版)。使用 LGPL 授權(quán)就可以利用 Qt 官方動態(tài)鏈接庫,而不必開放商業(yè)代碼。只要不修改和定制 Qt 庫,僅使用 Qt 官方發(fā)布的動態(tài)鏈接庫就可以不開源,這是商業(yè)友好的授權(quán)模式。
其實只要不是做商業(yè),就不太需要關(guān)注用什么授權(quán),以 GPL 授權(quán)發(fā)布程序代碼就可以了。
世界上的開源協(xié)議有很多,最后請認(rèn)準(zhǔn) Qt 官方網(wǎng)站(有時候訪問速度很慢甚至不能訪問,讀者請自備梯子),可以查閱文檔或者瀏覽資訊:https://www.qt.io/
世界上的 GUI 庫多如牛毛,有的跨平臺,有的專屬于某個操作系統(tǒng);有的只有 UI 功能,有的還融合了網(wǎng)絡(luò)通信、多媒體處理、數(shù)據(jù)庫訪問等底層功能。
Windows 下的 GUI 解決方案比較多:
基于 C++ 的有 Qt、MFC、WTL、wxWidgets、DirectUI、Htmlayout;
基于 C# 的有 WinForm、WPF;
基于 Java 的有 AWT、Swing;
基于 Pascal 的 有Delphi;
基于Go語言的有 walk 和 electron;
還有國內(nèi)初露頭角的 aardio;
Visual Basic 曾經(jīng)很流行,現(xiàn)在逐漸失去了色彩;
如果你有 Web 開發(fā)經(jīng)驗,也可以基于 Webkit 或 Chromium 將網(wǎng)頁轉(zhuǎn)換為桌面程序。
沒有哪一種方案能夠獨(dú)霸 Windows,使用比較多的編程語言是 C++、C#、Java。
用 Qt 來開發(fā) Windows 桌面程序有以下優(yōu)點(diǎn):
簡單易學(xué):Qt 封裝的很好,幾行代碼就可以開發(fā)出一個簡單的客戶端,不需要了解 Windows API。
資料豐富:資料豐富能夠成倍降低學(xué)習(xí)成本,否則你只能去看源碼,關(guān)于 DirectUI、Htmlayout、aardio 的資料就很少。
漂亮的界面:Qt 很容易做出漂亮的界面和炫酷的動畫,而 MFC、WTL、wxWidgets 比較麻煩。
獨(dú)立安裝:Qt 程序最終會編譯為本地代碼,不需要其他庫的支撐,而 Java 要安裝虛擬機(jī),C# 要安裝 .NET Framework。
跨平臺:如果你的程序需要運(yùn)行在多個平臺下,同時又希望降低開發(fā)成本,Qt 幾乎是必備的。
讀者經(jīng)常將 MFC 和 Qt 進(jìn)行對比,MFC 只能應(yīng)用在 Windows 平臺,而 Qt 是跨平臺的,一次編寫,到處運(yùn)行。
另外,Qt 已經(jīng)封裝了底層細(xì)節(jié),學(xué)習(xí) Qt 將會非常簡單;而 MFC 只是給 Windows API 加了一層包裝,不了解 Windows API 也學(xué)不好 MFC,大家普遍反映 MFC 難學(xué)。
我們不能簡單地說 Qt 好還是 MFC 好,兩者都有用武之地;但是初學(xué)者學(xué)習(xí) Qt 會比較簡單,不用應(yīng)付那些煩人的 Windows API,很快就能開發(fā)出帶有漂亮界面的應(yīng)用程序。
Linux 下常用的 GUI 庫有基于 C++ 的 Qt、GTK+、wxWidgets,以及基于 Java 的 AWT 和 Swing。其中最著名的就是 Qt 和 GTK+:KDE 桌面系統(tǒng)已經(jīng)將 Qt 作為默認(rèn)的 GUI 庫,Gnome 桌面系統(tǒng)也將 GTK+ 作為默認(rèn)的 GUI 庫。
相比 GTK+,Qt 的功能更加強(qiáng)大,更新也很快,比較受人們追捧。
Qt4 時代的主流就是傳統(tǒng)部件(或叫控件)編程,所用的語言一般是 C++。 Qt5 誕生之時,正是手機(jī)移動設(shè)備蓬勃發(fā)展的時候,而傳統(tǒng)的 C++ 部件編寫的界面對手機(jī)應(yīng)用程序非常方便,比如手機(jī)屏幕顯示隨意翻轉(zhuǎn), 這在傳統(tǒng)桌面程序里基本遇不到,誰會將 22 寸顯示器翻過來轉(zhuǎn)過去呢。
為了適應(yīng)手機(jī)移動應(yīng)用開發(fā), Qt5 將 QML 腳本編程提到與傳統(tǒng) C++ 部件編程相同的高度,力推 QML 界面編程,當(dāng)然 QML 主要用于手機(jī)移動應(yīng)用程序。 QML 包含大量使用手機(jī)移動設(shè)備的功能模塊,比如基本部件(QtQuick 模塊)、GPS 定位、渲染特效、藍(lán)牙、NFC、WebkKit 等等。
QML 類似于網(wǎng)頁設(shè)計的 HTML,是一種標(biāo)記語言,我們可以借助 CSS 對它進(jìn)行美化,也可以借助 JavaScript 進(jìn)行交互。有 Web 開發(fā)經(jīng)驗的讀者學(xué)習(xí) QML 將非常輕松。
使用 QML 開發(fā)界面主要有以下幾個優(yōu)點(diǎn):
QML 非常靈活,可以做出非常炫酷的效果,例如 QQ、360、迅雷等都不在話下。
QML 是標(biāo)記語言,見名知意,非常容易編寫和閱讀,大大提高了開發(fā)和維護(hù)效率。
QML 界面簡潔大氣,有很多動畫,適合移動端。
不同平臺下的 QML 使用相同的渲染機(jī)制,界面效果一致,不會隨操作系統(tǒng)的不同而變化。
既然 QML 有這么多優(yōu)點(diǎn),我們是不是可以不學(xué) C++,直接學(xué)習(xí) QML 呢?
非也!QML 只能用來進(jìn)行界面設(shè)計和人機(jī)交互,也就是只能勝任 UI 部分,在底層仍然需要調(diào)用 C++ 編寫的組件來完善功能,比如訪問數(shù)據(jù)庫、網(wǎng)絡(luò)通信、多線程多進(jìn)程、文件讀寫、圖像處理、音頻視頻處理等都離不開 C++。
另外,現(xiàn)階段新生的 QML 還不如傳統(tǒng)的 C++ 部件編程那樣擁有豐富的開發(fā)組件,尤其缺乏復(fù)雜的企業(yè)級應(yīng)用程序所必須的樹等控件。這就決定了至少現(xiàn)階段,真正大型的桌面程序仍然只能選擇以 C++ 為主、QML 為輔的開發(fā)模式。
相信大部分讀者都沒有 Web 開發(fā)經(jīng)驗,學(xué)習(xí) QML 成本還是比較高的,不但要習(xí)慣 QML 這種標(biāo)記性語言,還要學(xué)習(xí) CSS 和 JavaScript。
總的來說,C++ 對于 Qt 是不可或缺的,而 QML 只是一個加分項。
C++依舊是 Qt 的主要編程語言,Qt 5 也并沒有忽略它,Qt 5 添加了很多新的 C++ API,而且會持續(xù)更新。引入 QML 只是 Qt 5 提供的另外一種選擇,并不是讓它成為唯一的選擇。
C++ 是 Qt 的基礎(chǔ),無論如何都要掌握,本教程也只講解傳統(tǒng)的 C++ 部件編程,不講解 QML。
Qt 體積很大,有 1GB~3GB,官方下載通道非常慢,相信很多讀者會崩潰,所以建議大家使用國內(nèi)的鏡像網(wǎng)站(較快),或者使用迅雷下載(很快)。
作為 Qt 下載教程,本文會同時講解以上三種下載方式。
Qt 官方下載(非常慢)
Qt 官網(wǎng)有一個專門的資源下載網(wǎng)站,所有的開發(fā)環(huán)境和相關(guān)工具都可以從這里下載,具體地址是:http://download.qt.io/
目錄 | 說明 |
archive | 各種 Qt 開發(fā)工具安裝包,新舊都有(可以下載 Qt 開發(fā)環(huán)境和源代碼)。 |
community_releases | 社區(qū)定制的 Qt 庫,Tizen 版 Qt 以及 Qt 附加源碼包。 |
development_releases | 開發(fā)版,有新的和舊的不穩(wěn)定版本,在 Qt 開發(fā)過程中的非正式版本。 |
learning | 有學(xué)習(xí) Qt 的文檔教程和示范視頻。 |
ministro | 迷你版,目前是針對 Android 的版本。 |
official_releases | 正式發(fā)布版,是與開發(fā)版相對的穩(wěn)定版 Qt 庫和開發(fā)工具(可以下載Qt開發(fā)環(huán)境和源代碼)。 |
online | Qt 在線安裝源。 |
snapshots | 預(yù)覽版,最新的開發(fā)測試中的 Qt 庫和開發(fā)工具。 |
archive 和 official_releases 兩個目錄都有最新的 Qt 開發(fā)環(huán)境安裝包,我們以 archive 目錄里的內(nèi)容為例來說明。點(diǎn)擊進(jìn)入 archive 目錄,會看到四個子目錄:
目錄 | 說明 |
vsaddin | 這是 Qt 針對 Visual Studio 集成的插件,本教程基本不使用 Visual Studio ,所以不需要插件。 |
qtcreator | 這是 Qt 官方的集成開發(fā)工具,但是 qtcreator 本身是個空殼,它沒有編譯套件和 Qt 開發(fā)庫。 除了老版本的 Qt 4 需要手動下載 qtcreator、編譯套件、Qt 開發(fā)庫進(jìn)行搭配之外,一般用不到。對于我們教程壓根不需要下載它,因為 Qt 5 有專門的大安裝包,里面包含開發(fā)需要的東西,并且能自動配置好。 |
qt | 這是 Qt 開發(fā)環(huán)境的下載目錄,我們剛說的 Qt 5 的大安裝包就在這里面。 |
online_installers | 在線安裝器,國內(nèi)用戶不建議使用,在線安裝是龜速,還經(jīng)常斷線。我們教程采用的全部是離線的大安裝包。 |
我們再進(jìn)入 qt 子目錄 ,看到如下列表:
圖3:進(jìn)入 qt 子目錄
上圖沒有列完整,這個 qt 目錄包含了所有的 Qt 版本,從 1.0 到目前的 5.12 。
由于 Qt 5.9 是一個長期技術(shù)支持版本(Long Term Support,LTS),在未來幾年里都將有更新支持,因此,本教程以 Qt 5.9 LTS 版本為例進(jìn)行講解,并且所有實例程序均使用 Qt 5.9 編譯測試通過。
Qt 的上一個 LTS 版本是 5.6,它其實已經(jīng)超出支持期了。
進(jìn)入 5.9 目錄,會看到各種子版本:
圖4:Qt 5.9 的各個子版本
這里解釋一下 Qt 的版本號,比如 5.9.8 是完整的 Qt 版本號,第一個數(shù)字 5 是大版本號(major),第二個數(shù)字 9 是小版本號(minor),第三個數(shù)字 8 是補(bǔ)丁號(patch)。 只要前面兩個數(shù)字相同,Qt 的特性就是一致的,最后的數(shù)字是對該版本的補(bǔ)丁更新。也就是說本教程對 5.9.* 系列的 Qt 都是通用的,下載 5.9.* 任意一個版本都可以,這里我們以下載 5.9.0。
點(diǎn)擊 5.9.0,進(jìn)入子目錄:
圖5:Qt 5.9.0 下載頁面
根據(jù)不同的操作系統(tǒng),選擇不同的安裝包即可,不用管源碼包,除非你想自己編譯或者閱讀源碼。
我們以 Windows 安裝包(qt-opensource-windows-x86-5.9.0.exe)講解一下 Qt 安裝包命名規(guī)則,其中:
opensource 是指開源版本;
windows 是指開發(fā)環(huán)境的操作系統(tǒng);
x86 是指 32 位系統(tǒng);
5.9.0 是 Qt 版本號。
請讀者注意圖5中最后一欄的 Details 鏈接(紅色方框圈起來的地方)。點(diǎn)擊 Details 鏈接可以進(jìn)入詳情頁,在該頁面可以看到文件的大小、校驗和以及世界各地鏡像下載鏈接(這才是重點(diǎn))。
圖6:國內(nèi)鏡像下載鏈接
可以清楚地看到,Qt 在國內(nèi)的有三個鏡像網(wǎng)站可以下載,點(diǎn)擊這些地址中的一個就可以下載,從國內(nèi)鏡像網(wǎng)站下載速度快一些。
國內(nèi)鏡像網(wǎng)站
這里給大家推薦幾個國內(nèi)著名的 Qt 鏡像網(wǎng)站,主要是各個高校的:
中國科學(xué)技術(shù)大學(xué):http://mirrors.ustc.edu.cn/qtproject/
清華大學(xué):https://mirrors.tuna.tsinghua.edu.cn/qt/
北京理工大學(xué):http://mirror.bit.edu.cn/qtproject/
中國互聯(lián)網(wǎng)絡(luò)信息中心:https://mirrors.cnnic.cn/qt/
國內(nèi)鏡像網(wǎng)站的結(jié)構(gòu)和官方是類似的,我們在第一部分已經(jīng)分析過了,這里不再贅述。
將 Qt 軟件的下載地址復(fù)制到迅雷的下載框,如果迅雷官方有資源,就會自動識別,下載速度就很快了。
如何找到 Qt 軟件的下載地址呢?以清華大學(xué)開源軟件鏡像站為例,進(jìn)入 Qt 5.9.0 的下載目錄(https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.9/5.9.0/),在某個鏈接處單擊鼠標(biāo)右鍵,會彈出一個菜單,選擇“復(fù)制鏈接地址”,如下圖所示:
圖7:找到 Qt 下載地址
這樣就把 Qt 5.9.0 的下載地址(具體為 https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.9/5.9.0/qt-opensource-windows-x86-5.9.0.exe)復(fù)制到了剪切板,然后再粘貼到迅雷的下載框
點(diǎn)擊“立即下載”按鈕,稍等片刻,迅雷會自動匹配到資源,速度飛快。
注意,常用的 Qt 版本一般都能匹配到資源,但是不保證每個版本都能匹配到資源,上面的例子僅對清華大學(xué)鏡像站的 Qt 5.9.0 Windows 版(https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.9/5.9.0/qt-opensource-windows-x86-5.9.0.exe)有效。
擴(kuò)展閱讀
對 Qt 版本更新感興趣的讀者請訪問 Qt wiki 網(wǎng)站,地址為:https://wiki.qt.io/Main
Qt wiki 網(wǎng)站會顯示最新的正式版、LTS 版、正在開發(fā)中的版本等等,比主站(https://www.qt.io/)靠譜多了。Qt 主站因為商業(yè)推廣的原因,安裝包的下載步驟非常繁瑣。
本節(jié)介紹 Qt 5.9.0 在 Windows 平臺下的安裝,請?zhí)崆跋螺d好 Qt 5.9.0。不知道如何下載 Qt 的讀者請轉(zhuǎn)到:Qt下載(多種下載通道+所有版本)
目前較高版本的 Qt 僅支持 Win7 及其以后的操作系統(tǒng),不支持 Win XP;使用 Win XP 的讀者請安裝 Qt 5.5.1 之前的版本。
Qt 占用的存儲空間很大,安裝之前建議先準(zhǔn)備好 8GB 以上的磁盤空間。對于目前 Qt 最新版開發(fā)環(huán)境,如果不安裝源代碼包,實際占用大約 5.5GB;如果選擇安裝源碼包,大約占用 7.5GB。
雙擊下載得到的 qt-opensource-windows-x86-5.9.0.exe 即可開始安裝。Qt 的安裝過程和普通的 Windows 軟件一樣,按照向?qū)нM(jìn)行操作即可。
關(guān)于 Qt 的安裝需要說明以下幾點(diǎn)。
Qt 在安裝過程中會提示用戶進(jìn)行注冊和登錄,不用理會,跳過(Skip)即可,實際開發(fā)時不需要登錄。
圖1:Qt 安裝過程中提示用戶注冊
圖2:指定 Qt 安裝路徑
Qt 允許用戶自定義安裝路徑,但是請注意,安裝路徑不能帶空格、中文字符或者其它任何特殊字符。
另外,該界面還會詢問是否關(guān)聯(lián)特定的文件類型。如果關(guān)聯(lián)(默認(rèn)是關(guān)聯(lián)的),特定后綴的文件(包括 .cpp 文件)默認(rèn)使用 Qt 打開。我喜歡使用純文本編輯器(例如 Sublime Text)來打開 C++ 源文件,所以我取消了該選項,讀者根據(jù)自己的實際情況定奪。
3) 選擇安裝組件
Qt 安裝過程中最關(guān)鍵的一步是組件的選擇,請看下圖:
圖3:Qt 組件
Qt 的安裝組件分為兩部分:一部分是“Qt 5.9”分類下的,該分類包含的是真正的 Qt 開發(fā)庫組件;另一部分是“Tools”分類下的,該分類包含的是集成開發(fā)環(huán)境和編譯工具。
“Qt 5.9”分類下的開發(fā)組件 | |
組件 | 說明 |
MinGW 5.3.0 32 bit | 編譯器模塊。MinGW 是 Minimalist GNU for Windows 的縮寫,MinGW 是 Windows 平臺上使用的 GNU 工具集導(dǎo)入庫的集合。是本教程使用 MinGW 編譯,所以必須安裝。 |
UWP *** | UWP 是 Windows 10 中 Universal Windows Platform 的簡稱,有不同編譯器類型的 UWP,屬于 MSVC 編譯器生成的 Qt 庫。如果不是開發(fā) UWP 應(yīng)用程序,就不需要,直接忽略。 |
MSVC *** | 針對 Windows 平臺上的 MSVC 編譯器的 Qt 組件,如 msvc2015 32-bit 和 msvc2015 64-bit 等。安裝該組件需要計算機(jī)上已經(jīng)安裝相應(yīng)版本的 Visual Studio。如果你不使用 MSVC 編譯器進(jìn)行開發(fā),就不用安裝。本教程使用 MinGW 編譯組件,所以不用安裝 MSVC *** 組件。 |
Android *** | 這是針對安卓應(yīng)用開發(fā)的 Qt 庫,如果讀者有安卓開發(fā)這方面需求可以自己選擇安裝,一般情況下用不到。 |
Sources | Qt 的源代碼包,除非你想閱讀 Qt 的源碼,否則不用安裝。 |
Qt *** | Qt 的附加模塊,大部分建議安裝,這些附加模塊括號里的 TP 是指 Technology Preview ,技術(shù)預(yù)覽模塊的意思,還處在功能測試階段,不是正式版模塊;附加模塊括號里的 Deprecated 是指拋棄的舊模塊,兼容舊代碼使用的,一般用不到。這些附加模塊讀者可以選擇部分或都勾選了安裝,占用空間不大。 部分組件說明:Qt Charts 是二維圖表模塊,用于繪制柱狀圖、餅圖、曲線圖等常用二維圖表。Qt Data Visualization 是三維數(shù)據(jù)圖表模塊,用于數(shù)據(jù)的三維顯示,如散點(diǎn)的三維空間分布、三維曲面等。Qt Scritp(Deprecated)是腳本模塊,已被拋棄,不建議安裝。 |
“Tools”分類下的開發(fā)組件 | |
組件 | 說明 |
Qt Creator 4.3.0 | 這是集成開發(fā)環(huán)境,強(qiáng)制安裝的,以后所有的項目和代碼都在 Qt Creator 里面新建和編輯。 |
Qt Creator 4.3.0 CDB Debugger surpport | 用于和 CDB 調(diào)試工具對接,默認(rèn)安裝,一般用于調(diào)試 VC 編譯的 Qt 程序。 |
MinGW 5.3.0 | 這是開源的編譯器套件,這本教程必須用到的,需要讀者勾選安裝。 |
Strawberry Perl 5.22.1.3 | 用于編譯 Qt 源代碼的 Perl 開發(fā)環(huán)境,不需要安裝。如果讀者以后用到,也可以另外手動安裝,在搜索引擎搜索 Strawberry Perl 關(guān)鍵詞,去 Strawberry Perl 官網(wǎng)下載最新的安裝包是一樣用的。 |
選擇完了組件,根據(jù)向?qū)б徊揭徊讲僮骶涂梢粤恕0惭b完成后,在 Windows“開始”菜單中會看到 Qt 5.9.0 程序組。
圖4:Qt 5.9.0 所包含的程序
程序 | 說明 |
Qt Creator 4.6.2 (Enterprise) | Qt 的集成開發(fā)環(huán)境,本教程就使用它來創(chuàng)建和管理 Qt 項目。 |
Assistant(Qt 助手) | 用來查看幫助文檔,已被集成在 Qt Creator 中。 |
Designer(Qt 設(shè)計師) | 圖形界面可視化編輯工具,已被集成在 Qt Creator 中,在 Qt Creator 中編輯或創(chuàng)建界面文件時,就可以自動打開。 |
Linguist(Qt 語言家) | 多國語言翻譯支持工具,可以用來編輯語言資源文件,在開發(fā)多語言界面的應(yīng)用程序時會用到。 |
Qt 5.11.1 for Desktop (MinGW 5.3.0 32bit) | Qt 命令行工具,用來配置 Qt 開發(fā)環(huán)境(主要是設(shè)置 PATH 變量)。 |
了解 Qt 安裝目錄的結(jié)構(gòu)雖然不是編程必須的,但是它能練就我們的內(nèi)功,讓我們對 Qt 的編程環(huán)境了如指掌。Windows 和 Linux 下 Qt 安裝目錄的結(jié)構(gòu)非常相似,我們以 Windows 為例進(jìn)行講解,Linux 不再贅述。
不同版本 Qt 的安裝目錄結(jié)構(gòu)大同小異,本節(jié)我們以 Qt 5.9.0 為例來說明,如下圖所示。
圖1:Qt 安裝目錄的結(jié)構(gòu)
為了方便描述,下文我們使用~表示 Qt 的安裝目錄。
注意,~.9\ 和 ~\Tools\ 目錄下都有 mingw53_32 目錄(圖中我用紅色標(biāo)出來了),但是兩者是有區(qū)別的:
~.9\mingw53_32\ 目錄包含的是 Qt 的類庫文件,例如頭文件、靜態(tài)庫、動態(tài)庫等,這些類庫文件使用 MinGW 工具集編譯而成。
~\Tools\mingw53_32\ 目錄包含的是 MinGW 工具集,例如編譯器 g++、鏈接器 ld、make 工具、打包工具 ar 等。
QtCreator 是個例外,QtCreator 使用 MSVC2015 編譯生成的,所以安裝目錄里有一個 vcredist 文件夾存儲 VC 運(yùn)行庫安裝文件。
最后的 MaintenanceTool.exe ,對于離線安裝包,它只能用于刪除軟件包,如果 Qt 開發(fā)環(huán)境是用在線安裝方式裝的,這個工具還可以管理開發(fā)環(huán)境組件和升級組件。
Qt 類庫的幫助文件位于 Docs 文件夾里,需要用 Qt Assistant 工具才能查看。
Examples 里是示例代碼,可以用 QtCreator 集成開發(fā)環(huán)境打開各個示例。
下面我們再探究一下 Qt 類庫目錄(~.9\mingw53_32\)的結(jié)構(gòu),如下圖所示。
圖2:Qt 類庫目錄
圖上列的比較有限,不一定全面,主要是教大家熟悉一下 Qt 的開發(fā)環(huán)境。
Qt 不是憑空產(chǎn)生的,它是基于現(xiàn)有工具鏈打造而成的,它所使用的編譯器、鏈接器、調(diào)試器等都不是自己的,Qt 官方只是開發(fā)了上層工具。下面我們分幾個部分講解 Qt 使用到的工具鏈。
在上個世紀(jì)八十年代,計算機(jī)都是奢侈品,操作系統(tǒng)里最著名的是 Unix 家族, 當(dāng)時還沒有 Windows、Linux 之類的,Unix 系統(tǒng)都是商業(yè)軟件,里面的應(yīng)用軟件也是商業(yè)軟件, 全是封閉的環(huán)境。
系統(tǒng)程序員 Richard M. Stallman (RMS) 在此環(huán)境下創(chuàng)立了與眾不同的 GNU 項目 (GNU’s Not Unix) , 以及推進(jìn)自由軟件發(fā)展的 Free Software Foundation (FSF) 自由軟件基金會。
GNU 項目是為了創(chuàng)建自由的類 Unix 系統(tǒng),也因此開發(fā)出來很多開源的系統(tǒng)工具,其中非常著名的就是 GCC (GNU Compiler Collection,GNU編譯器套件)。
現(xiàn)在我們知道,GUN 開發(fā)類 Unix 系統(tǒng)的項目失敗了,但是它開發(fā)的一系列工具集卻用到了后來的 Linux 內(nèi)核上,兩者結(jié)合形成了今天的各種 Linux 發(fā)行版
在 GNU 工具集里面,開發(fā)時常見到的幾個羅列如下(這些工具通常位于 Linux 或 Unix 系統(tǒng)里的 /usr/bin/ 目錄):
工具 | 說明 |
gcc | GNU C 語言編譯器。 |
g++ | GNU C++ 語言編譯器。 |
ld | GNU 鏈接器,將目標(biāo)文件和庫文件鏈接起來,創(chuàng)建可執(zhí)行程序和動態(tài)鏈接庫。 |
ar | 生成靜態(tài)庫 .a ,可以編輯和管理靜態(tài)鏈接庫。 |
make | 生成器,可以根據(jù) makefile 文件自動編譯鏈接生成可執(zhí)行程序或庫文件。 |
gdb | 調(diào)試器,用于調(diào)試可執(zhí)行程序。 |
ldd | 查看可執(zhí)行文件依賴的共享庫(擴(kuò)展名 .so,也叫動態(tài)鏈接庫)。 |
原本 GNU 工具只在 Linux/Unix 系統(tǒng)里才有,隨著 Windows 系統(tǒng)的廣泛使用, 為了在 Windows 系統(tǒng)里可以使用 GNU 工具,誕生了 MinGW(Minimalist GNU for Windows) 項目,利用 MinGW 就可以生成 Windows 里面的 exe 程序和 dll 鏈接庫。
需要注意的是,MinGW 與 Linux/Unix 系統(tǒng)里 GNU 工具集的有些區(qū)別:
MinGW 里面工具帶有擴(kuò)展名 .exe, Linux/Unix 系統(tǒng)里工具通常都是沒有擴(kuò)展名的。
MinGW 里面的生成器文件名為 mingw32-make.exe,Linux/Unix 系統(tǒng)里就叫 make。
MinGW 在鏈接時是鏈接到 *.a 庫引用文件,生成的可執(zhí)行程序運(yùn)行時依賴 *.dll,而 Linux/Unix 系統(tǒng)里鏈接時和運(yùn)行時都是使用 *.so 。
另外 MinGW 里也沒有 ldd 工具,因為 Windows 不使用 .so 共享庫文件。如果要查看 Windows 里可執(zhí)行文件的依賴庫,需要使用微軟自家的 Dependency Walker 工具。Windows 里面動態(tài)庫擴(kuò)展名為 .dll,MinGW 可以通過 dlltool 來生成用于創(chuàng)建和使用動態(tài)鏈接庫需要的文件,如 .def 和 .lib。
MinGW 原本是用于生成 32 位程序的,隨著 64 位系統(tǒng)流行起來, 從 MinGW 分離出來了 MinGW-w64 項目,該項目同時支持生成 64 位和 32 位程序。Qt 的 MinGW 版本庫就是使用 MinGW-w64 項目里面的工具集生成的。
另外提一下,由于 MinGW 本身主要就是編譯鏈接等工具和頭文件、庫文件,并不包含系統(tǒng)管理、文件操作之類的 Shell 環(huán)境, 這對希望用類 Unix 命令的開發(fā)者來說還是不夠用的。 所以 MinGW 官方又推出了 MSYS(Minimal SYStem),相當(dāng)于是一個部署在 Windows 系統(tǒng)里面的小型 Unix 系統(tǒng)環(huán)境, 移植了很多 Unix/Linux 命令行工具和配置文件等等,是對 MinGW 的擴(kuò)展。
MSYS 對于熟悉 Unix/Linux 系統(tǒng)環(huán)境或者要嘗試學(xué)習(xí) Unix/Linux 系統(tǒng)的人都是一種便利。MSYS 和 MinGW 的安裝升級都是通過其官方的 mingw-get 工具實現(xiàn),二者是統(tǒng)一下載安裝管理的。
【文章福利】:Qt開發(fā)學(xué)習(xí)資料包、Qt面試題文檔、項目視頻、學(xué)習(xí)路線,包括(Qt C++基礎(chǔ),數(shù)據(jù)庫編程,Qt項目實戰(zhàn)、Qt框架、QML、Opencv、qt線程等等),免費(fèi)分享,有需要的可以加君羊領(lǐng)取哦!~學(xué)習(xí)交流君羊937552610點(diǎn)擊加入領(lǐng)取資料
對于 MinGW-w64 項目,它對應(yīng)的小型系統(tǒng)環(huán)境叫 MSYS2(Minimal SYStem 2),MSYS2 是 MSYS 的衍生版,不僅支持 64 位系統(tǒng)和 32 位系統(tǒng),還有自己的獨(dú)特的軟件包管理工具,它從 Arch Linux 系統(tǒng)里移植了 pacman 軟件管理工具,所以裝了 MSYS2 之后,可以直接通過 pacman 來下載安裝軟件,而且可以自動解決依賴關(guān)系、方便系統(tǒng)升級等。裝了 MSYS2 之后,不需要自己去下載 MinGW-w64,可以直接用 pacman 命令安裝編譯鏈接工具和 git 工具等。
MinGW 項目主頁(含 MSYS): http://www.mingw.org/
MinGW-w64 項目主頁: https://sourceforge.net/projects/mingw-w64/
MSYS2 項目主頁: https://sourceforge.net/projects/msys2/
CMake(Cross platform Make)是一個開源的跨平臺自動化構(gòu)建工具, 可以跨平臺地生成各式各樣的 makefile 或者 project 文件, 支持利用各種編譯工具生成可執(zhí)行程序或鏈接庫。
CMake 自己不編譯程序, 它相當(dāng)于用自己的構(gòu)建腳本 CMakeLists.txt,叫各種編譯工具集去生成可執(zhí)行程序或鏈接庫。
一般用于編譯程序的 makefile 文件比較復(fù)雜,自己去編寫比較麻煩, 而利用 CMake ,就可以編寫相對簡單的 CMakeLists.txt ,由 CMake 根據(jù) CMakeLists.txt 自動生成 makefile,然后就可以用 make 生成可執(zhí)行程序或鏈接庫。
本教程里面是使用 Qt 官方的 qmake 工具生成 makefile 文件,沒有用 CMake。這里之所以提 CMake,是因為整個 KDE 桌面環(huán)境的茫茫多程序都是用 CMake 腳本構(gòu)建的,另外跨平臺的程序/庫如 Boost C++ Libraries、OpenCV、LLVM、Clang 等也都是用 CMake 腳本構(gòu)建的。以后如果接觸到這些東西,是需要了解 CMake 的。
CMake 項目主頁:https://cmake.org/
KDE 項目主頁:https://www.kde.org/
Qt 官方的開發(fā)環(huán)境安裝包里有自己專門的開發(fā)工具,之前用過 qmake 命令。qmake 是 Qt 開發(fā)最核心的工具,既可以生成 Qt 項目文件 .pro ,也可以自動生成項目的 Makefile 文件。
這里將常用的 Qt 開發(fā)工具列表如下:
工具 | 說明 |
qmake | 核心的項目構(gòu)建工具,可以生成跨平臺的 .pro 項目文件,并能依據(jù)不同操作系統(tǒng)和編譯工具生成相應(yīng)的 Makefile,用于構(gòu)建可執(zhí)行程序或鏈接庫。 |
uic | User Interface Compiler,用戶界面編譯器,Qt 使用 XML 語法格式的 .ui 文件定義用戶界面,uic 根據(jù) .ui 文件生成用于創(chuàng)建用戶界面的 C++ 代碼頭文件,比如 ui_*****.h 。 |
moc | Meta-Object Compiler,元對象編譯器,moc 處理 C++ 頭文件的類定義里面的 Q_OBJECT 宏,它會生成源代碼文件,比如 moc_*****.cpp ,其中包含相應(yīng)類的元對象代碼,元對象代碼主要用于實現(xiàn) Qt 信號/槽機(jī)制、運(yùn)行時類型定義、動態(tài)屬性系統(tǒng)。 |
rcc | Resource Compiler,資源文件編譯器,負(fù)責(zé)在項目構(gòu)建過程中編譯 .qrc 資源文件,將資源嵌入到最終的 Qt 程序里。 |
qtcreator | 集成開發(fā)環(huán)境,包含項目生成管理、代碼編輯、圖形界面可視化編輯、 編譯生成、程序調(diào)試、上下文幫助、版本控制系統(tǒng)集成等眾多功能, 還支持手機(jī)和嵌入式設(shè)備的程序生成部署。 |
assistant | Qt 助手,幫助文檔瀏覽查詢工具,Qt 庫所有模塊和開發(fā)工具的幫助文檔、示例代碼等都可以檢索到,是 Qt 開發(fā)必備神器,也可用于自學(xué) Qt。 |
designer | Qt 設(shè)計師,專門用于可視化編輯圖形用戶界面(所見即所得),生成 .ui 文件用于 Qt 項目。 |
linguist | Qt 語言家,代碼里用 tr() 宏包裹的就是可翻譯的字符串,開發(fā)人員可用 lupdate 命令生成項目的待翻譯字符串文件 .ts,用 linguist 翻譯多國語言 .ts ,翻譯完成后用 lrelease 命令生成 .qm 文件,然后就可用于多國語言界面顯示。 |
qmlscene | 在 Qt 4.x 里是用 qmlviewer 進(jìn)行 QML 程序的原型設(shè)計和測試,Qt 5 用 qmlscene 取代了舊的 qmlviewer。新的 qmlscene 另外還支持 Qt 5 中的新特性 scenegraph 。 |
本節(jié)我們來介紹一下使用 Qt 編程過程中常用的術(shù)語和名字,它們不一定專屬于 Qt,在其它的 C/C++ 開發(fā)過程中也會使用到。
Project
Project 的中文翻譯是“項目”或者“工程”,這里的項目是指為實現(xiàn)某個相對獨(dú)立功能的程序代碼合集,這些代碼不單單是放在一塊,而是有相互之間的關(guān)聯(lián)性,并且有專門負(fù)責(zé)管理該項目的項目文件,比如:
Qt 使用 .pro 文件管理項目;
VC++ 則使用 .vcproj 作為項目文件。
集成開發(fā)環(huán)境通常都是依據(jù)項目文件(.pro/.vcproj)管理和構(gòu)建項目。
Makefile
即生成腳本,雖然可以直接調(diào)用編譯器如 g++ 編譯程序,但是如果項目里的代碼文件變多了,哪些代碼文件更新了需要重新編譯,哪些代碼沒有改不需要重新編譯等等,靠程序員自己記憶去處理是比較麻煩的事,還有哪些代碼需要預(yù)處理或是鏈接哪些庫文件, 這些都是繁雜的過程。為了規(guī)范程序的編譯生成過程,產(chǎn)生了規(guī)范化的生成腳本,就是 Makefile,生成器 make 可以依據(jù)規(guī)范的 Makefile 自動生成目標(biāo)程序或庫文件。
簡單的說,就是定義好 Makefile ,讓程序員只需要去關(guān)注如何編寫代碼,而生成程序過程中的臟活累活都交給 make 程序。
現(xiàn)在 Makefile 通常都有工具自動生成,如 qmake 工具, 這樣就大量減輕了程序員的負(fù)擔(dān)。
Debug 和 Release
Debug 即調(diào)試,Release 即發(fā)行。代碼編寫之后,生成的目標(biāo)程序或庫文件通常不會絕對正確,或多或少有些毛病(bug), 因此需要進(jìn)行糾錯調(diào)試(Debug)。調(diào)試過程中需要源代碼和二進(jìn)制目標(biāo)程序之間一一對應(yīng)的關(guān)系, 這樣才能定位到錯誤代碼,所以 Debug 版本的程序是臃腫而不進(jìn)行優(yōu)化的。
與之相對的是 Release 發(fā)行版,在糾正了發(fā)覺到的錯誤后,需要發(fā)布程序用于實際用途,實際應(yīng)用時強(qiáng)調(diào)運(yùn)行效率高,減少冗余代碼,因此會對二進(jìn)制程序進(jìn)行大量優(yōu)化,提升性能。這樣發(fā)布的二進(jìn)制目標(biāo)程序就是 Release 版。
Debug 版本和 Release 版本使用的庫文件不一樣:
Debug 版本程序通常鏈接的也是 Debug 版本的庫文件,比如 libQt5Guid.a/Qt5Guid.dll,庫文件的簡短名(不含擴(kuò)展名)都是以 d 結(jié)尾的,Debug 庫通常都比較大 。
Release 版本程序鏈接的通常就是 Release 版本的庫文件,Release 版本庫文件名字比 Debug 版本庫文件少一個字母 d ,如 libQt5Gui.a/Qt5Gui.dll,而且 Release 版本庫一般都比 Debug 版本小很多,運(yùn)行效率也高很多。
C++11 標(biāo)準(zhǔn)
時代在變化,C++ 標(biāo)準(zhǔn)也在前進(jìn)。C++ 正式公布標(biāo)準(zhǔn)有 C++98、C++03、C++11。最新的 C++11 標(biāo)準(zhǔn)是2011年8月12日公布的,在公布之前該標(biāo)準(zhǔn)原名為 C++0x 。這是一次較大的修訂和擴(kuò)充,建議讀者專門學(xué)一下。
Qt 從 4.8 版本就開始用 C++11 新特性了。編譯器里面開始支持 C++11 的版本是 MSVC 2010、GCC 4.5、Clang 3.1,這之后版本的編譯器都在逐步完善對 C++11 的支持,現(xiàn)在新版本編譯器對新標(biāo)準(zhǔn)的支持都比較全面了。
Qt 官方在編譯 Qt5 庫的時候都是開啟 C++11 特性的,如果我們要在自己項目代碼啟用新標(biāo)準(zhǔn),需要在 .pro 文件里面添加一行:
CONFIG +=c++11
如果是 Qt4 版本則是添加:
gcc:CXXFLAGS +=-std=c++0x
MSVC 編譯器默認(rèn)開啟 C++11 特性,GCC(g++命令)則需要自己添加選項 -std=c++0x ,上面 CXXFLAGS 就是為 GCC 編譯器(g++命令)添加 -std=c++0x 選項。
Dynamic Link 即動態(tài)鏈接,Static Link 即靜態(tài)鏈接。
動態(tài)鏈接庫
目標(biāo)程序通常都不是獨(dú)立個體,生成程序時都需要鏈接其他的庫,要用到其他庫的代碼。對于多個程序同時運(yùn)行而言,內(nèi)存中就可能有同一個庫的多個副本,占用了太多內(nèi)存而干的活差不多。
為了優(yōu)化內(nèi)存運(yùn)用效率,引入了動態(tài)鏈接庫(Dynamic Link Library),或叫共享庫(Shared Object)。使用動態(tài)鏈接庫時,內(nèi)存中只需要一份該庫文件,其他程序要使用該庫文件時,只要鏈接過來就行了。由于動態(tài)庫文件外置,鏈接到動態(tài)庫的目標(biāo)程序相對比較小,因為剝離了大量庫代碼,而只需要一些鏈接指針。
使用動態(tài)庫,也意味著程序需要鏈接到如 *.dll 或 *.so 文件,得提前裝好動態(tài)庫文件,然后目標(biāo)程序才能正常運(yùn)行。
靜態(tài)鏈接庫
靜態(tài)庫就是將鏈接庫的代碼和自己編寫的代碼都編譯鏈接到一塊,鏈接到靜態(tài)庫的程序通常比較大,但好處是運(yùn)行時依賴的庫文件很少,因為目標(biāo)程序自己內(nèi)部集成了很多庫代碼。
庫文件后綴
Linux/Unix 系統(tǒng)里靜態(tài)庫擴(kuò)展名一般是 .a,動態(tài)庫擴(kuò)展名一般是 .so 。Windows 系統(tǒng)里 VC 編譯器用的靜態(tài)庫擴(kuò)展名一般是 .lib,動態(tài)庫擴(kuò)展名一般是 .dll 。
MinGW 比較特殊,是將 GNU 工具集和鏈接庫從 Linux/Unix 系統(tǒng)移植到 Windows 里, 有意思的情況就出現(xiàn)了,MinGW 使用的靜態(tài)庫擴(kuò)展名為 .a ,而其動態(tài)庫擴(kuò)展名則為 .dll, .a 僅在生成目標(biāo)程序過程中使用,.dll 則是在目標(biāo)程序運(yùn)行時使用。
Explicit Linking 和 Implicit Linking
Explicit Linking 即顯式鏈接,Implicit Linking 即隱式鏈接,這兩種都是動態(tài)鏈接庫的使用方式。
動態(tài)鏈接庫通常都有其導(dǎo)出函數(shù)列表, 告知其他可執(zhí)行程序可以使用它的哪些函數(shù)。可執(zhí)行程序使用這些導(dǎo)出函數(shù)有兩種方式:一是在運(yùn)行時使用主動加載動態(tài)庫的函數(shù),Linux 里比如用 dlopen 函數(shù)打開并加載動態(tài)庫,Windows 里一般用 LoadLibrary 打開并加載動態(tài)庫,只有當(dāng)程序代碼執(zhí)行到這些函數(shù)時,其參數(shù)里的動態(tài)庫才會被加載,這就是顯式鏈接。顯式鏈接方式是在運(yùn)行時加載動態(tài)庫,其程序啟動時并不檢查這些動態(tài)庫是否存在。
隱式鏈接是最為常見的,所有的編譯環(huán)境默認(rèn)都是采用隱式鏈接的方式使用動態(tài)庫。隱式鏈接會在鏈接生成可執(zhí)行程序時就確立依賴關(guān)系,在該程序啟動時,操作系統(tǒng)自動會檢查它依賴的動態(tài)庫,并一一加載到該程序的內(nèi)存空間,程序員就不需要操心什么時候加載動態(tài)庫了。比如 VC 編譯環(huán)境,鏈接時使用動態(tài)庫對應(yīng)的 .lib 文件(包含動態(tài)庫的導(dǎo)出函數(shù)聲明,但沒有實際功能代碼),在 .exe 程序運(yùn)行前系統(tǒng)會檢查依賴的 .dll,如果找不到某個動態(tài)庫就會出現(xiàn)類似下圖對話框:
MinGW 是將動態(tài)庫的導(dǎo)出函數(shù)聲明放在了 .a 文件里,程序運(yùn)行依賴的動態(tài)庫也是 .dll 。
請注意,VC 鏈接器使用的 .lib 文件分兩類,一種是完整的靜態(tài)庫,體積比較大,另一種是動態(tài)庫的導(dǎo)出聲明,體積比較小。MinGW 鏈接器使用的 .a 文件也是類似的,Qt 官方庫都是按照動態(tài)庫發(fā)布的,靜態(tài)庫只有自己編譯才會有。
啟動 Qt Creator,出現(xiàn)如圖 1 所示的主窗口:
圖 1 Qt Creator主窗口
Qt Creator 的界面很簡潔。上方是主菜單欄,左側(cè)是主工具欄,窗口的中間部分是工作區(qū)。根據(jù)設(shè)計內(nèi)容不同,工作區(qū)會顯示不同的內(nèi)容。
圖 1 是在左側(cè)主工具欄單擊“Welcome(歡迎)”按鈕后顯示實例的界面。這時工作區(qū)的左側(cè)有 “Projects”、“Examples(示例)”、“Tutorials(教程)”、“Get Started Now”幾個按鈕,單擊后會在主工作區(qū)顯示相應(yīng)的內(nèi)容:
單擊“Projects”按鈕后,工作區(qū)顯示新建項目按鈕和最近打開項目的列表。
單擊“Examples(示例)”按鈕后,工作區(qū)顯示 Qt 自帶的大量實例,選擇某個實例就可以在 Qt Creator 中打開該項目源程序。
單擊“Tutorials(教程)”按鈕后,工作區(qū)顯示各種視頻教程,查看視頻教程需要聯(lián)網(wǎng)并使用瀏覽器打開。
單擊“Get Started Now”按鈕,工作區(qū)顯示“Qt Creator Manual”幫助主題內(nèi)容。
主窗口左側(cè)是主工具欄,主工具欄提供了項目文件編輯、窗體設(shè)計、程序調(diào)試、項目設(shè)置等各種功能按鈕。
對 Qt Creator 可以進(jìn)行一些設(shè)置,如剛安裝好的 Qt Creator 界面語言可能是中文,也可以選擇將 Qt Creator 的界面語言設(shè)置為英文。
圖 2 Options 的構(gòu)建和運(yùn)行設(shè)置頁面
單擊 Qt Creator 菜單欄的 Tools→Options 菜單項會打開選項設(shè)置對話框(如圖 2 所示)。對話框的左側(cè)是可設(shè)置的內(nèi)容分組,單擊后右側(cè)出現(xiàn)具體的設(shè)置界面。常用的設(shè)置包括以下幾點(diǎn):
Environment(環(huán)境) 設(shè)置:在 Interface 頁面可以設(shè)置語言和主題,本教程全部以中文界面的 Qt Creator 進(jìn)行講解,所以語言選擇為 Chinese(China);為了使界面抓圖更清晰,設(shè)置主題為 Flat Light。更改語言和主題后需要重新啟動 Qt Creator 才會生效。
Text Editor(文本編輯器)設(shè)置:在此界面可以設(shè)置文本編輯器的字體,設(shè)置各種類型文字的字體顏色,如關(guān)鍵字、數(shù)字、字符串、注釋等字體顏色,也可以選擇不同的配色主題。編輯器缺省字體的大小為 9,可以修改得大一些。
Build & Run(構(gòu)建和運(yùn)行)設(shè)置:圖 2 顯示的是 Build & Run 的設(shè)置界面,它有以下幾個頁面。
Kits(構(gòu)建套件)頁面顯示 Qt Creator 可用的編譯工具。
Qt Versions 頁面顯示安裝的 Qt 版本。
Compliers(編譯器)頁面顯示系統(tǒng)里可用的 C 和 C++ 編譯器,由于安裝了 MinGW 和 Visual Studio 2015,Qt Creator 會自動檢測出這些編譯器。
Debuggers 頁面顯示 Qt Creator 自動檢測到的調(diào)試器,有 GNU gdb for MinGW 調(diào)試器和 Windows 的 CDB 調(diào)試器。
注意,如果只是在計算機(jī)上安裝了 Visual Studio 2015,圖 2 Kits 顯示的界面上 MSVC2015 的兩個編譯器的圖標(biāo)會變?yōu)閹в懈袊@號的一個黃色圖標(biāo)。Debuggers 頁面沒有 Windows 的 CDB 調(diào)試器,可以用 MSVC 編譯器對 Qt Creator 編寫的程序進(jìn)行編譯,但是不能調(diào)試,這是因為缺少了 Windows Software Development Kit (SDK)。這個 SDK 不會隨 Visual Studio 一同安裝,需要從 Microsoft 網(wǎng)站上下載。可以下載 Windows Software Development Kit (SDK) for Windows 8.1,安裝后重啟計算機(jī)即可。
學(xué)習(xí)一種編程語言或編程環(huán)境,通常會先編寫一個“Hello World”程序。我們也用 Qt Creator 編寫一個“Hello World”程序,以初步了解 Qt Creator 設(shè)計應(yīng)用程序的基本過程,對使用 Qt Creator 編寫 Qt C++ 應(yīng)用程序建立初步的了解。
單擊 Qt Creator 的菜單項文件->新建文件或項目,出現(xiàn)如圖 1 所示的對話框。在這個對話框里選擇需要創(chuàng)建的項目或文件的模板。
圖 1 新建文件或項目對話框
Qt Creator 可以創(chuàng)建多種項目,在最左側(cè)的列表框中單擊“Application”,中間的列表框中列出了可以創(chuàng)建的應(yīng)用程序的模板,各類應(yīng)用程序如下:
Qt Widgets Application,支持桌面平臺的有圖形用戶界面(Graphic User Interface,GUI) 界面的應(yīng)用程序。GUI 的設(shè)計完全基于 C++ 語言,采用 Qt 提供的一套 C++ 類庫。
Qt Console Application,控制臺應(yīng)用程序,無 GUI 界面,一般用于學(xué)習(xí) C/C++ 語言,只需要簡單的輸入輸出操作時可創(chuàng)建此類項目。
Qt Quick Application,創(chuàng)建可部署的 Qt Quick 2 應(yīng)用程序。Qt Quick 是 Qt 支持的一套 GUI 開發(fā)架構(gòu),其界面設(shè)計采用 QML 語言,程序架構(gòu)采用 C++ 語言。利用 Qt Quick 可以設(shè)計非常炫的用戶界面,一般用于移動設(shè)備或嵌入式設(shè)備上無邊框的應(yīng)用程序的設(shè)計。
Qt Quick Controls 2 Application,創(chuàng)建基于 Qt Quick Controls 2 組件的可部署的 Qt Quick 2 應(yīng)用程序。Qt Quick Controls 2 組件只有 Qt 5.7 及以后版本才有。
Qt Canvas 3D Application,創(chuàng)建 Qt Canvas 3D QML 項目,也是基于 QML 語言的界面設(shè)計,支持 3D 畫布。
在圖 1 顯示的對話框中選擇項目類型為 Qt Widgets Application 后,單擊“Choose…”按鈕,出現(xiàn)如圖 2 所示的新建項目向?qū)В?/p>
圖 2 新建項目向?qū)У?1 步:項目名稱和項目存儲位置設(shè)置
在圖 2 中,選擇一個目錄,如“E:\QtDemo”,再設(shè)置項目名稱為 Demo, 這樣新建項目后,會在“E:\QtDemo”目錄下新建一個目錄,項目所有文件保 存在目錄“E:\QtDemo\Demo\”下。
在圖 2 中設(shè)置好項目名稱和保存路徑后,單擊“Next”按鈕,出現(xiàn)如圖 3 所示的選擇編譯工具的界面:
圖 3 新建項目向?qū)У?2 步:選擇編譯工具
可以將這幾個編譯工具都選中,在編譯項目時再選擇一個作為當(dāng)前使用的編譯工具,這樣可以編譯生成不同版本的可執(zhí)行程序。
圖 4 新建項目想到第 3 步:選擇界面基類
在圖 3 顯示的界面中單擊“Next”按鈕,出現(xiàn)如圖 4 所示的界面。在此界面中選擇需要創(chuàng)建界面的基類(base class)。有 3 種基類可以選擇:
QMainWindow 是主窗口類,主窗口具有主菜單欄、工具欄和狀態(tài)欄,類似于一般的應(yīng)用程序的主窗口;
QWidget 是所有具有可視界面類的基類,選擇 QWidget 創(chuàng)建的界面對各種界面組件都可以 支持;
QDialog 是對話框類,可建立一個基于對話框的界面;
在此選擇 QMainWindow 作為基類,自動更改的各個文件名不用手動去修改。勾選“創(chuàng)建界面”復(fù)選框。這個選項如果勾選,就會由 Qt Creator 創(chuàng)建用戶界面文件,否則,需要自己編程手工創(chuàng)建界面。初始學(xué)習(xí),為了了解 Qt Creator 的設(shè)計功能,勾選此選項。
然后單擊“Next”按鈕,出現(xiàn)一個頁面,總結(jié)了需要創(chuàng)建的文件和文件保存目錄,單擊“完成”按鈕就可以完成項目的創(chuàng)建。
完成了以上新建項目的步驟后,在 Qt Creator 的左側(cè)工具欄中單擊“編輯”按鈕,可顯示如圖 5 所示的窗口。
圖 5 項目管理與文件編輯界面
窗口左側(cè)有上下兩個子窗口,上方的目錄樹顯示了項目內(nèi)文件的組織結(jié)構(gòu),顯示當(dāng) 前項目為 Demo。項目的名稱構(gòu)成目錄樹的一個根節(jié)點(diǎn),Qt Creator 可以打開多個項目,但是只有一個活動項目,活動項目的項目名稱節(jié)點(diǎn)用粗體字體表示。
在項目名稱節(jié)點(diǎn)下面,分組管理著項目內(nèi)的各種源文件,幾個文件及分組分別為以下幾項:
Demo.pro 是項目管理文件,包括一些對項目的設(shè)置項。
Headers 分組,該節(jié)點(diǎn)下是項目內(nèi)的所有頭文件(.h),圖 5 中所示項目有一個頭文件 mainwindow.h,是主窗口類的頭文件。
Sources 分組:該節(jié)點(diǎn)下是項目內(nèi)的所有 C++源文件(.cpp),圖 5 中所示項目有兩個 C++ 源文件,mainwindow.cpp 是主窗口類的實現(xiàn)文件,與 mainwindow.h 文件對應(yīng)。main.cpp 是主函數(shù)文件,也是應(yīng)用程序的入口。
Forms 分組:該節(jié)點(diǎn)下是項目內(nèi)的所有界面文件(.ui)。圖 5 中所示項目有一個界面文件mainwindow.ui,是主窗口的界面文件。界面文件是文本文件,使用 XML 語言描述界面的組成。
左側(cè)上下兩個子窗口的顯示內(nèi)容可以通過其上方的一個下拉列表框進(jìn)行選擇,可以選擇的顯示內(nèi)容包括項目、打開文檔、書簽、文件系統(tǒng)、類視圖、大綱等。在圖 5 中,上方的子窗口顯示了項目的文件目錄樹,下方顯示打開的文件列表。可以在下方選擇顯示類視圖,這樣下方則顯示項目內(nèi)所有的類的結(jié)構(gòu),便于程序瀏覽和快速切換到需要的代碼位置。
雙擊文件目錄樹中的文件mainwindow.ui,出現(xiàn)如圖 6 所示的窗體設(shè)計界面:
圖 6 集成在 Qt Creator 中UI設(shè)計器
這個界面實際上是 Qt Creator 中集成的 Qt Designer。窗口左側(cè)是分組的組件面板,中間是設(shè)計的窗體。在組件面板的 Display Widgets 分組里,將一個Label組件拖放到設(shè)計的窗體上面。雙擊剛剛放置的 Label 組件,可以編輯其文字內(nèi)容,將文字內(nèi)容更改為“Hello, World!”。還可以在窗口右下方的屬性編輯器里編輯標(biāo)簽的 Font 屬性,Point Size(點(diǎn)大小)更改為 12,勾選粗體。
單擊主窗口左側(cè)工具欄上的“項目”按鈕,出現(xiàn)如圖 7 所示的項目編譯設(shè)置界面。
圖 7 項目編譯器選擇和設(shè)置界面
界面左側(cè)一欄的“Build & Run”下面顯示了本項目中可用的編譯器工具,要使用哪一個編譯器用于項目編譯,單擊其名稱即可,選擇的編譯器名稱會用粗體字表示。這里選擇使用 MinGW 32bit 編譯器。
每個編譯器又有 Build 和 Run 兩個設(shè)置界面。在 Build 設(shè)置界面上,有一個“Shadow build” 復(fù)選框。如果勾選此項,編譯后將在項目的同級目錄下建立一個編譯后的文件目錄,目錄名稱包含編譯器信息,這種方式一般用于使用不同編譯器創(chuàng)建不同版本的可執(zhí)行文件。如果不勾選此項,編譯后將在項目的目錄下建立“Debug”和“Release”子目錄用于存放編譯后的文件。
在設(shè)計完 mainwindow.ui 文件,并設(shè)置好編譯工具之后,就可以對項目進(jìn)行編譯、調(diào)試或運(yùn)行。主窗口左側(cè)工具欄下方有 4 個按鈕,其功能見表 1。
圖標(biāo) | 作用 | 快捷鍵 |
電腦img | 彈出菜單選擇編譯工具和編譯模式,如 Debug或 Release模式 | |
綠三角img | 直接運(yùn)行程序,如果修改后未編譯,會先進(jìn)行編譯。即使在程序中設(shè)置了斷點(diǎn),此方式運(yùn)行的程序也無法調(diào)試。 | Ctrl+R |
綠三角甲蟲img | 項目需要以Debug模式編譯,點(diǎn)此按鈕開始調(diào)試運(yùn)行,可以在程序中設(shè)置斷點(diǎn)。若是以 Release模式編譯,點(diǎn)此按鈕也無法進(jìn)行調(diào)試。 | F5 |
錘子img | 編譯當(dāng)前項目 | Ctrl+B |
首先對項目進(jìn)行編譯,沒有錯誤后,再運(yùn)行程序。程序運(yùn)行的界面如圖 8 所示。這就是一個標(biāo)準(zhǔn)的桌面應(yīng)用程序,我們采用可視化的方式設(shè)計了一個窗口,并在上面顯示了字符串“Hello, World!”。
圖 8 實例程序 Demo 運(yùn)行時界面
在 Qt Creator 中也可以對程序設(shè)置斷點(diǎn)進(jìn)行調(diào)試,但是必須以 Debug 模式編譯,并以“Start Debugging”(快捷鍵 F5)方式運(yùn)行程序。
程序調(diào)試的方法與一般 IDE 工具類似,不再詳述。注意,要在 Qt Creator 里調(diào)試 MSVC2015 編譯的程序,必須安裝 Windows 軟件開發(fā)工具包 SDK。
前面章節(jié)中,通過在 xxx.ui 文件中拖拽 Label 組件,設(shè)計出了一個顯示 “Hello,World!” 的窗口,如下圖所示:
圖 1 簡單的界面
本節(jié)我們完全舍棄 xxx.ui 文件,親手編寫代碼實現(xiàn)圖 1 所示的界面。
首先,打開 Qt Creator 并創(chuàng)建一個 Qt Widgets Application 項目。需要注意的是,我們要創(chuàng)建一個不帶 xxx.ui 文件的項目,如下圖所示:
圖 2 創(chuàng)建不帶 ui 文件的圖形界面項目
最終創(chuàng)建的項目結(jié)構(gòu)如下圖所示:
圖 3 項目結(jié)構(gòu)
Demo.pro 是項目文件,文件中的內(nèi)容可以手動修改,本節(jié)不需要修改此文件。接下來,我們逐一介紹 main.cpp、mainwindow.h 和 mainwindow.cpp 這 3 個文件。
1) main.cpp
main.cpp 是主函數(shù)文件,內(nèi)部主要包含應(yīng)用程序的入口函數(shù),也就是 main() 函數(shù)。
我們知道,C/C++ 程序中 main() 函數(shù)的語法格式是固定的:
int main(int argc, char *argv[]){
//填充代碼
return 0;
}
Qt 界面程序中的 main() 函數(shù)也有固定的格式:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//填充代碼
return a.exec();
}
對于剛剛學(xué)習(xí) Qt 的讀者,暫時不用了解第 3 行和第 5 行代碼的含義,只要記住:使用 Qt 框架編寫帶界面的應(yīng)用程序,main() 函數(shù)中必須包含第 3 行和第 5 行代碼,否則程序無法正常運(yùn)行。
雙擊圖 3 所示的 main.cpp 文件,可以看到該文件包含的所有代碼:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
除了第 6、8 行代碼外,其它代碼的含義分別是:
1~2 行:由于 main() 函數(shù)中分別定義了 QApplication 和 MainWindow 類的對象,因此需要引入 mainwindows.h 和 QApplication 頭文件。mainwindow.h 文件是我們自己創(chuàng)建的,引入時用" "雙引號括起來;QApplication 是 Qt 提供給我們的,引入時用<>括起來。
第 7 行:MainWindow 是自定義的類,繼承自 QMainWindow 主窗口類,因此 MainWindow 也是一個主窗口類。w 是 MainWindow 類實例化出的對象,表示一個主窗口。
第 8 行:默認(rèn)情況下,Qt 提供的所有組件(控件、部件)都是隱藏的,不會自動顯示。通過調(diào)用 MainWindow 類提供的 show() 方法,w 窗口就可以在程序運(yùn)行后顯示出來。
2) mainwindow.h和mainwindow.cpp
創(chuàng)建項目時,我們在圖 2 所示的對話框中定義了一個繼承自 QMainWindow 的主窗口類,并起名為 MianWindow,該類的定義部分位于 mainwindow.h 頭文件中,實現(xiàn)部分位于 mainwindow.cpp 源文件中。
雙擊圖 3 所示的 mainwindow.h 和 mainwindow.cpp 文件,可以看到它們各自包含的代碼:
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent=0);
~MainWindow();
};
//mainwindow.cpp
#endif // MAINWINDOW_H
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
}
MainWindow::~MainWindow()
{
}
初始狀態(tài)下,MainWindow 類由 Q_OBJECT、構(gòu)造函數(shù)和析構(gòu)函數(shù)組成,這里重點(diǎn)介紹一下 Q_OBJECT 和構(gòu)造函數(shù):
Q_OBJECT:本質(zhì)是一個已定義好的宏,所有需要“信號和槽”功能的組件都必須將 Q_OBJECT 作為 private 屬性成員引入到類中。本節(jié)設(shè)計的界面程序不會用到“信號和槽”,因此可以刪除 Q_OBJECT。有關(guān)信號和槽,我們會在《Qt信號和槽機(jī)制詳解》一節(jié)詳細(xì)介紹。
帶參的構(gòu)造函數(shù):QWidget 是所有組件的基類,借助 parent 指針,可以為當(dāng)前窗口指定父窗口。例如圖 1 中,QLabel 文本框位于主窗口中,主窗口就是它的父窗口。當(dāng)父窗口被刪除時,所有子窗口也會隨之一起刪除。當(dāng)然也可以不指定父窗口,那么當(dāng)前窗口就會作為一個獨(dú)立的窗口,不會受到其它窗口的影響。
直接運(yùn)行程序,會輸出下圖所示的界面:
圖 4 空白主窗口
圖 4 看到的就是 main() 函數(shù)中創(chuàng)建的 w 主窗口。由于沒有往 w 窗口中放置任何組件,所以 w 是一個空白窗口。
我們嘗試向 w 主窗口添加一個文本框,需要對 MainWindow 類進(jìn)行修改。修改后的 MainWindow 類如下:
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel> // 引入 QLable 文件框組件的頭文件
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent=0);
~MainWindow();
private:
QLabel *lab; // 定義一個私有的 QLabel 指針對象
};
#endif // MAINWINDOW_H
//mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 創(chuàng)建一個 QLable 對象
this->lab=new QLabel("Hello,World!",this);
}
MainWindow::~MainWindow()
{
}
和先前空的 MainWindow 類相比,做了如下修改:
添加了一個 QLabel 類的指針對象,相應(yīng)地要引入<QLabel>頭文件;
在構(gòu)造函數(shù)中定義了一個 QLabel 類的文本框?qū)ο螅ㄟ^調(diào)用它的帶參構(gòu)造函數(shù),設(shè)置它的父對象為當(dāng)前類的對象,同時設(shè)置 “Hello,World!” 為要顯示的文本信息。
再次運(yùn)行程序,顯示的窗口如下圖所示:
圖 5 帶文本框的主窗口
圖 1 和圖 5 是類似的,區(qū)別在于圖 5 中的 “Hello, World!” 沒有加粗,也沒有調(diào)整它在主窗口中的位置,這些都可以通過編碼實現(xiàn),后續(xù)講 QLabel 時會做詳細(xì)介紹。
圖 5 中,“Hello,World!” 文本框的父窗口是主窗口,所以文本框位于主窗口中(位置默認(rèn)在窗口的左上角),主窗口關(guān)閉時文本框也會隨之關(guān)閉。
由此,我們就成功設(shè)計了一個包含文本框的窗口,這也是我們編寫的第一個 Qt 程序。
Qt 是一個著名的 GUI 框架,用來開發(fā)和用戶交互的圖形界面。作為 GUI 框架,豐富的控件和靈活的事件機(jī)制是不可或缺的,Qt 在這一方面做得非常優(yōu)秀。
Qt 控件又稱組件或者部件,指用戶看到的所有可視化界面以及界面中的各個元素,比如按鈕、文本框、輸入框等。
為了方便程序員開發(fā),Qt 提供了很多現(xiàn)成的控件。打開某個帶 ui 文件的 Qt Widgets Application 項目,ui 文件的 Widget Box 一欄展示了 Qt 提供的幾乎所有控件:
圖 1 Qt 提供的控件
Qt 中的每個控件都由特定的類表示,每個控件類都包含一些常用的屬性和方法,所有的控件類都直接或者間接繼承自 QWidget 類。實際開發(fā)中,我們可以使用 Qt 提供的這些控件,也可以通過繼承某個控件類的方式自定義一個新的控件。
前面說過,Qt 中所有可視化的元素都稱為控件,我們習(xí)慣將帶有標(biāo)題欄、關(guān)閉按鈕的控件稱為窗口。例如,下圖展示了兩種常用的窗口,實現(xiàn)它們的類分別是 QMainWindow 和 QDialog。
圖 2 Qt 窗口
QMainWindow 類生成的窗口自帶菜單欄、工具欄和狀態(tài)欄,中央?yún)^(qū)域還可以添加多個控件,常用來作為應(yīng)用程序的主窗口;
QDialog 類生成的窗口非常簡單,沒有菜單欄、工具欄和狀態(tài)欄,但可以添加多個控件,常用來制作對話框。
除了 QMainWindow 和 QDialog 之外,還可以使用 QWidget 類,它的用法非常靈活,既可以用來制作窗口,也可以作為某個窗口上的控件。
窗口很少單獨(dú)使用,它的內(nèi)部往往會包含很多控件。例如圖 2 中,我們分別往 MainWindow 和 Dialog 窗口中放置了一個按鈕控件,根據(jù)需要還可以放置更多的控件。當(dāng)窗口彈出時,窗口包含的所有控件會一同出現(xiàn);當(dāng)窗口關(guān)閉時,窗口上的所有控件也會隨之消失。
實際開發(fā)中,制作應(yīng)用程序的主窗口可以用 QMainWindow 或者 QWdiget;制作一個提示信息的對話框就用 QDialog 或 QWidget;如果暫時無法決定,后續(xù)可能作為窗口,也可能作為控件,就選擇 QWidget。
簡單地理解,Qt 事件指的是應(yīng)用程序和用戶之間的交互過程,例如用戶按下某個按鈕,點(diǎn)擊某個輸入框等等。實際上除了用戶會與應(yīng)用程序進(jìn)行交互外,操作系統(tǒng)也會與應(yīng)用程序進(jìn)行交互,例如當(dāng)某個定時任務(wù)觸發(fā)時,操作系統(tǒng)會關(guān)閉應(yīng)用程序,這也是一個事件。
Qt 界面程序的 main() 主函數(shù)中首先要創(chuàng)建一個 QApplication 類的對象,函數(shù)執(zhí)行結(jié)束前還要調(diào)用 QApplication 對象的 exec() 函數(shù)。一個 Qt 界面程序要想接收事件,main() 函數(shù)中就必須調(diào)用 exec() 函數(shù),它的功能就是使程序能夠持續(xù)不斷地接收各種事件。
Qt 程序可以接收的事件種類有很多,例如鼠標(biāo)點(diǎn)擊事件、鼠標(biāo)滾輪事件、鍵盤輸入事件、定時事件等。每接收一個事件,Qt 會分派給相應(yīng)的事件處理函數(shù)來處理。所謂事件處理函數(shù),本質(zhì)就是一個普通的類成員函數(shù),以用戶按下某個 QPushButton 按鈕為例,Qt 會分派給 QPushButton 類中的 mousePressEvent() 函數(shù)處理。
事件處理函數(shù)通常會完成兩項任務(wù),分別是:
創(chuàng)建一個不帶 ui 文件的 Qt Widgets Application 項目,項目中只保留一個 main.cpp 源文件,刪除其它文件(mainwindows.h 和 mainwindow.cpp)。將下述代碼直接拷貝到 main.cpp 文件:
//main.cpp
#include <QApplication>
#include <QWidget>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//添加窗口
QWidget widget;
//定義一個按鈕,它位于 widget 窗口中
QPushButton But("按鈕控件",&widget);
//設(shè)置按鈕的位置和尺寸
But.setGeometry(10,10,100,50);
//信號與槽,實現(xiàn)當(dāng)用戶點(diǎn)擊按鈕時,widget 窗口關(guān)閉
QObject::connect(&But,&QPushButton::clicked,&widget,&QWidget::close);
//讓 widget 窗口顯示
widget.show();
return a.exec();
}
運(yùn)行結(jié)果如下圖所示:
圖 3 運(yùn)行結(jié)果
整個程序的運(yùn)行過程如下:
先創(chuàng)建了一個 QWidget 窗口,在窗口上添加一個 QPushButton 按鈕;
當(dāng)用戶點(diǎn)擊按鈕時,Qt 會將此事件分派給 QPushButton 類的 mousePressEvent() 事件處理函數(shù);
mousePressEvent()函數(shù)內(nèi)部會改變按鈕的屬性,提示用戶成功按下了按鈕,還會調(diào)用 clicked() 函數(shù)發(fā)出“用戶點(diǎn)擊按鈕”的信號。對于發(fā)出的信號,程序中調(diào)用 connect() 函數(shù)指定 QWidget 類的 close() 函數(shù)接收 clicked() 信號,close() 函數(shù)會關(guān)閉 widget 窗口。
實際開發(fā)中,我們用各種 Qt 控件設(shè)計出功能豐富的界面,用 Qt 事件完成與用戶的交互。學(xué)習(xí) Qt 界面編程,本質(zhì)上就是學(xué)習(xí) Qt 各個控件的用法以及對 Qt 事件的處理。本節(jié)我們只是對 Qt 控件和事件做了簡單的了解,接下來將為您系統(tǒng)地講解它們的用法。
信號和槽是 Qt 特有的消息傳輸機(jī)制,它能將相互獨(dú)立的控件關(guān)聯(lián)起來。
舉個簡單的例子,按鈕和窗口本是兩個獨(dú)立的控件,點(diǎn)擊按鈕并不會對窗口造成任何影響。通過信號和槽機(jī)制,我們可以將按鈕和窗口關(guān)聯(lián)起來,實現(xiàn)“點(diǎn)擊按鈕會使窗口關(guān)閉”的效果。
在 Qt 中,用戶和控件的每次交互過程稱為一個事件,比如“用戶點(diǎn)擊按鈕”是一個事件,“用戶關(guān)閉窗口”也是一個事件。每個事件都會發(fā)出一個信號,例如用戶點(diǎn)擊按鈕會發(fā)出“按鈕被點(diǎn)擊”的信號,用戶關(guān)閉窗口會發(fā)出“窗口被關(guān)閉”的信號。
Qt 中的所有控件都具有接收信號的能力,一個控件還可以接收多個不同的信號。對于接收到的每個信號,控件都會做出相應(yīng)的響應(yīng)動作。例如,按鈕所在的窗口接收到“按鈕被點(diǎn)擊”的信號后,會做出“關(guān)閉自己”的響應(yīng)動作;再比如輸入框自己接收到“輸入框被點(diǎn)擊”的信號后,會做出“顯示閃爍的光標(biāo),等待用戶輸入數(shù)據(jù)”的響應(yīng)動作。在 Qt 中,對信號做出的響應(yīng)動作就稱為槽。
圖 1 信號和槽
信號和槽機(jī)制底層是通過函數(shù)間的相互調(diào)用實現(xiàn)的。每個信號都可以用函數(shù)來表示,稱為信號函數(shù);每個槽也可以用函數(shù)表示,稱為槽函數(shù)。例如,“按鈕被按下”這個信號可以用 clicked() 函數(shù)表示,“窗口關(guān)閉”這個槽可以用 close() 函數(shù)表示,信號和槽機(jī)制實現(xiàn)“點(diǎn)擊按鈕會關(guān)閉窗口”的功能,其實就是 clicked() 函數(shù)調(diào)用 close() 函數(shù)的效果。
信號函數(shù)和槽函數(shù)通常位于某個類中,和普通的成員函數(shù)相比,它們的特別之處在于:
信號函數(shù)用 signals 關(guān)鍵字修飾,槽函數(shù)用 public slots、protected slots 或者 private slots 修飾。signals 和 slots 是 Qt 在 C++ 的基礎(chǔ)上擴(kuò)展的關(guān)鍵字,專門用來指明信號函數(shù)和槽函數(shù);
信號函數(shù)只需要聲明,不需要定義(實現(xiàn)),而槽函數(shù)需要定義(實現(xiàn))。
為了提高程序員的開發(fā)效率,Qt 的各個控件類都提供了一些常用的信號函數(shù)和槽函數(shù)。例如 QPushButton 類提供了 4 個信號函數(shù)和 5 個 public slots 屬性的槽函數(shù),可以滿足大部分場景的需要。
Qt Creator 提供了很強(qiáng)大的 Qt GUI 開發(fā)手冊,很容易就能查到某個控件類中包含哪些信號函數(shù)和槽函數(shù)。舉個例子,查看 QPushButton 類中信號函數(shù)和槽函數(shù)的過程是:
在程序中引入<QPushButton>頭文件,雙擊選中“QPushButton”并按 “Fn+F1” 快捷鍵,就會彈出 QPushButton 類的使用手冊,如下圖所示。
圖 2 QPushButton類的使用說明
在 Contents 部分可以看到,QPushButton 類只提供了一些Public Slots屬性的槽函數(shù),沒有提供信號函數(shù)。對于 QPushButton 類按鈕,除了可以使用自己類提供的槽函數(shù),還可以使用從父類繼承過來的信號函數(shù)和槽函數(shù)。由圖 2 可知,QPushButton 的父類是 QAbstractButton,點(diǎn)擊 QAbstractButton 就可以直接跳轉(zhuǎn)到此類的使用手冊,如下圖所示:
圖 3 QPushButton父類使用說明
QAbstractButton 類中既有 Signals 信號函數(shù),也有 Public Slots 槽函數(shù),這里不再一一列舉,感興趣的讀者可以自行查看。
注意,并非所有的控件之間都能通過信號和槽關(guān)聯(lián)起來,信號和槽機(jī)制只適用于滿足以下條件的控件:
控件類必須直接或者間接繼承自 QObject 類。Qt 提供的控件類都滿足這一條件,這里提供一張 Qt常用類的繼承關(guān)系的高清圖片,感興趣的讀者可以簡單了解一下。
控件類中必須包含 private 屬性的 Q_OBJECT 宏。
將某個信號函數(shù)和某個槽函數(shù)關(guān)聯(lián)起來,需要借助 QObject 類提供的 connect() 函數(shù)。
connect() 是 QObject 類中的一個靜態(tài)成員函數(shù),專門用來關(guān)聯(lián)指定的信號函數(shù)和槽函數(shù)。
關(guān)聯(lián)某個信號函數(shù)和槽函數(shù),需要搞清楚以下 4 個問題:
信號發(fā)送者是誰?
哪個是信號函數(shù)?
信號的接收者是誰?
哪個是接收信號的槽函數(shù)?
仍以實現(xiàn)“按下按鈕后窗口關(guān)閉”為例,先創(chuàng)建一個窗口和一個按鈕,如下所示:
QWidget widget;
//定義一個按鈕,它位于 widget 窗口中
QPushButton But("按鈕控件",&widget);
信號發(fā)送者是 But 按鈕對象,要發(fā)送的信號是“按鈕被點(diǎn)擊”,可以用 QPushButton 類提供的 clicked() 信號函數(shù)表示;信號的接收者是 widget 主窗口對象,“窗口關(guān)閉”作為信號對應(yīng)的槽,可以用 QWidget 類提供的 close() 函數(shù)表示。
在 Qt5 版本之前,connect() 函數(shù)最常用的語法格式是:
QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type=Qt::AutoConnection)
各個參數(shù)的含義分別是:
sender:指定信號的發(fā)送者;
signal:指定信號函數(shù),信號函數(shù)必須用 SIGNAL() 宏括起來;
reveiver:指定信號的接收者;
method:指定接收信號的槽函數(shù),槽函數(shù)必須用 SLOT() 宏括起來;
type 用于指定關(guān)聯(lián)方式,默認(rèn)的關(guān)聯(lián)方式為 Qt::AutoConnection,通常不需要手動設(shè)定。
用 connect() 函數(shù)將 But 按鈕的 clicked() 信號函數(shù)和 widget 窗口的 close() 槽函數(shù)關(guān)聯(lián)起來,實現(xiàn)代碼如下:
connect(&But, SIGNAL(clicked()), &widget, SLOT(close()));
如此就實現(xiàn)了“按下按鈕會關(guān)閉窗口”的功能。
Qt5 版本中,connect() 函數(shù)引入了新的用法,常用的語法格式是:
QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type=Qt::AutoConnection)
和舊版本相比,新版的 connect() 函數(shù)改進(jìn)了指定信號函數(shù)和槽函數(shù)的方式,不再使用 SIGNAL() 和 SLOT() 宏。
例如,用新版 connect() 函數(shù)關(guān)聯(lián) But 按鈕的 clicked() 信號函數(shù)和 widget 窗口的 close() 槽函數(shù),實現(xiàn)代碼為:
connect(&But, &QPushButton::clicked, &widget, &QWidget::close);
可以看到,新版 connect() 函數(shù)指定信號函數(shù)和槽函數(shù)的語法格式是&+函數(shù)所在類+函數(shù)名。
一個 connect() 函數(shù)只能關(guān)聯(lián)一個信號函數(shù)和一個槽函數(shù),程序中可以包含多個 connect() 函數(shù),能實現(xiàn)以下幾種效果:
關(guān)聯(lián)多個信號函數(shù)和多個槽函數(shù);
一個信號函數(shù)可以關(guān)聯(lián)多個槽函數(shù),當(dāng)信號發(fā)出時,與之關(guān)聯(lián)的槽函數(shù)會一個接一個地執(zhí)行,但它們執(zhí)行的順序是隨機(jī)的,無法人為指定哪個先執(zhí)行、哪個后執(zhí)行;
多個信號函數(shù)可以關(guān)聯(lián)同一個槽函數(shù),無論哪個信號發(fā)出,槽函數(shù)都會執(zhí)行。
此外,connect() 函數(shù)的 method 參數(shù)還可以指定一個信號函數(shù),也就是說,信號之間也可以相互關(guān)聯(lián),這樣當(dāng)信號發(fā)出時,會隨之發(fā)出另一個信號。
創(chuàng)建一個不含 ui 文件的 Qt Widgets Application 項目,只保留 main.cpp 源文件,刪除 mainwindow.h 和 mainwindow.cpp 文件。在 main.cpp 文件中編寫如下代碼:
#include <QApplication>
#include <QWidget>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//添加窗口
QWidget widget;
//定義一個按鈕,它位于 widget 窗口中
QPushButton But("按鈕控件",&widget);
//設(shè)置按鈕的位置和尺寸
But.setGeometry(10,10,100,50);
//信號與槽,實現(xiàn)當(dāng)用戶點(diǎn)擊按鈕時,widget 窗口關(guān)閉
QObject::connect(&But,&QPushButton::clicked,&widget,&QWidget::close);
//讓 widget 窗口顯示
widget.show();
return a.exec();
}
運(yùn)行結(jié)果為:
如上圖所示,由于使用了 conect() 函數(shù)將 But 的 clicked() 信號函數(shù)和 widget 的 close() 槽函數(shù)關(guān)聯(lián)起來,所以生成了“點(diǎn)擊按鈕后主窗口關(guān)閉”的效果。
QLabel 是 Qt 幫我們寫好的一個控件類,間接繼承自 QWidget 類,它的繼承關(guān)系如下:
QLabel -> QFrame -> QWidget
從字面上理解,QLabel 可以解釋為“Qt 的 Label”,即 Qt 提供給我們的一種文本控件,它的基礎(chǔ)功能是顯示一串文本。例如,下圖就是一個普通的文本框:
圖 1 QLabel控件
除了顯示一串文本外,QLabel 控件上還可以放置圖片、超鏈接、動畫等內(nèi)容。例如:
圖 2 QLabel放置圖片和超鏈接文字
本質(zhì)上,每個文本框都是 QLabel 類的一個實例對象。QLabel 類提供了兩個構(gòu)造函數(shù),分別是:
QLabel(QWidget *parent=Q_NULLPTR, Qt::WindowFlags f=Qt::WindowFlags())
QLabel(const QString &text, QWidget *parent=Q_NULLPTR, Qt::WindowFlags f=Qt::WindowFlags())
各個參數(shù)的含義分別是:
text 參數(shù)用于指定文本框中顯示的文字;
parent 參數(shù)用于指定文本框的父窗口;
WindowFlags 是一種枚舉類型,f 參數(shù)用來設(shè)置文本框的一些系統(tǒng)屬性和外觀屬性,默認(rèn)值為 Qt::Widget,表示當(dāng)不指定父窗口時,文本框?qū)⒆鳛橐粋€獨(dú)立窗口(如圖 1、2 所示),反之則作為父窗口中的一個控件。f 參數(shù)的可選值有很多,比如 Qt::Window 表示文本框?qū)⒆鳛橐粋€獨(dú)立的窗口,它自帶邊框和標(biāo)題欄,Qt::ToolTip 表示文本框?qū)⒆鳛橐粋€提示窗口,不帶邊框和標(biāo)題欄等等,這里不再一一列舉。
需要注意的是,第一個構(gòu)造函數(shù)中的 parent 和 f 參數(shù)都有默認(rèn)值,因此 QLabel 類還隱含了一個默認(rèn)構(gòu)造函數(shù)。也就是說,創(chuàng)建 QLable 對象時可以不傳遞任何參數(shù),或者只給 txt 參數(shù)傳遞一個字符串,就可以成功創(chuàng)建一個文本框。通常情況下,我們會給 text 和 parent 參數(shù)傳遞相應(yīng)的值,即在創(chuàng)建文本框的同時指定文本內(nèi)容和父窗口。
QLabel 類本身提供有很多屬性和方法,它還從父類繼承了很多屬性和方法。下表給大家羅列了 QLabel 類常用的一些屬性和方法:
屬 性 | 含 義 |
alignment | 保存 QLabel 控件中內(nèi)容的對齊方式,默認(rèn)情況下,QLabel 控件中的內(nèi)容保持左對齊和垂直居中。 該屬性的值可以通過調(diào)用 alignment() 方法獲得,可以借助 setAlignment() 方法修改。 |
text | 保存 QLabel 控件中的文本,如果 QLabel 控件中沒有文本,則 text 的值為空字符串, 該屬性的值可以通過 text() 方法獲得,可以借助 setText() 方法修改。 |
pixmap | 保存 QLabel 控件內(nèi)顯示的圖片,如果控件內(nèi)沒有設(shè)置圖片,pixmap 的值為 0。 該屬性的值可以通過調(diào)用 pixmap() 方法獲得,可以借助 setPixmap() 方法修改。 |
selectedText | 保存 QLabel 控件中被選擇了的文本,當(dāng)沒有文本被選擇時,selectedText 的值為空字符串。 該屬性的值可以通過調(diào)用 selectedText() 方法獲得。 |
hasSelectedText | 判斷用戶是否選擇了 QLabel 控件內(nèi)的部分文本,如果是則返回 true,反之則返回 false。默認(rèn)情況下,該屬性的值為 false。 |
indent | 保存 QLabel 控件內(nèi)文本的縮進(jìn)量,文本的縮進(jìn)方向和 alignment 屬性的值有關(guān)。 該屬性的值可以通過調(diào)用 indent() 方法獲得,可以借助 setIndent() 方法修改。 |
margin | 保存 QLabel 控件中內(nèi)容與邊框之間的距離(邊距),margin 的默認(rèn)值為 0。 該屬性的值可以通過調(diào)用 margin() 方法獲得,可以借助 setMargin() 方法修改。 |
wordWrap | 保存 QLabel 控件內(nèi)文本的換行策略。當(dāng)該屬性的值為 true 時,控件內(nèi)的文本會在必要時自動換行。默認(rèn)情況下,控件內(nèi)的文本是禁止自動換行的。 該屬性的值可以通過 wordWrap() 方法獲得,可以借助 setWordWrap() 方法修改。 |
除了上表中提到了獲取和修改屬性值得成員方法外,下表給大家羅列了一些常用的操作 QLabel 控件的成員方法,它們有些定義在 QLabel 類內(nèi),有些是通過繼承父類得到的:
成員方法 | 功 能 |
hide() | 隱藏文本框。 |
clear() | 清空 QLabel 控件內(nèi)所有顯示的內(nèi)容。 |
setToolTip(QString) | 設(shè)置信息提示,當(dāng)用戶的鼠標(biāo)放在QLabel 文本框上時會自動跳出文字。 |
setToolTipDuration(int) | 設(shè)置提示信息出現(xiàn)的時間,單位是毫秒。 |
setStyleSheet(QString) | 設(shè)置 QLabel 文本框的樣式。 |
setGeometry(int x, int y, int w, int h) | 設(shè)置 QLabel 文本框的位置 (x, y) 以及尺寸 (w, h)。 |
QLabel 控件只用來顯示文本、圖像等內(nèi)容,很好與用戶交互。但是,當(dāng) QLabel 控件內(nèi)包含超鏈接內(nèi)容時,可以使用 QLabel 類提供的兩個信號函數(shù):
信號函數(shù) | 功 能 |
linkActivated(const QString &link) | 用戶點(diǎn)擊超鏈接時觸發(fā),link 參數(shù)用于向槽函數(shù)傳輸超鏈接的 URL。 |
linkHovered(const QString &link) | 用戶的鼠標(biāo)懸停到超鏈接位置時觸發(fā),link 參數(shù)用于向槽函數(shù)傳輸超鏈接的 URL。 |
QLabel 控件提供了很多槽函數(shù),如下表所示:
槽函數(shù) | 功 能 |
clear() | 清空 QLabel 控件內(nèi)所有的內(nèi)容。 |
setMovie(QMovie *movie) | 清空 QLabel 控件內(nèi)所有的內(nèi)容,改為顯示指定的 movie 動畫。 |
setNum(int num) | 清空 QLabel 控件內(nèi)所有的內(nèi)容,改為顯示 num 整數(shù)的值。 |
setNum(double num) | 清空 QLabel 控件內(nèi)所有的內(nèi)容,改為顯示 num 小數(shù)的值。 |
setPicture(const QPicture &picture) | 清空 QLabel 控件內(nèi)所有的內(nèi)容,改為顯示經(jīng) QPicture 類處理的圖像。 |
setPixmap(const QPixmap &) | 清空 QLabel 控件內(nèi)所有的內(nèi)容,改為顯示經(jīng) QPixmap 類處理的圖像。 |
setText(const QString &) | 清空 QLabel 控件內(nèi)所有的內(nèi)容,改為顯示指定的文本。 |
除了表 2、3 羅列的這些信號和槽函數(shù)外,QLabel 類還從父類處繼承了一些信號和槽函數(shù),這里不再一一羅列。
接下來通過一個實例,給大家演示 QLabel 控件中一些屬性和方法的用法。
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建一個文本框
QLabel lab;
//設(shè)置文本框內(nèi)容居中顯示
lab.setAlignment(Qt::AlignCenter);
//設(shè)置文本框的坐標(biāo)和尺寸
lab.setGeometry(100,100,400,400);
//設(shè)置文本框的外觀,包括字體的大小和顏色、按鈕的背景色
lab.setStyleSheet("QLabel{font:30px;color:red;background-color:rgb(f9,f9,f9);}");
//設(shè)置文本框要顯示超鏈接內(nèi)容
lab.setText("<a href=\"http://www.baidu.net\">老舅");
//當(dāng)用戶鼠標(biāo)位于文本框上時,顯示提示內(nèi)容
lab.setToolTip("點(diǎn)擊超鏈接顯示URL");
//提示內(nèi)容顯示 1 秒
lab.setToolTipDuration(1000);
//為文本框設(shè)置信號和槽,當(dāng)用戶點(diǎn)擊超鏈接時,將文本框內(nèi)容改為超鏈接的 URL
QObject::connect(&lab,&QLabel::linkActivated,&lab,&QLabel::setText);
//程序運(yùn)行后,文本框顯示
lab.show();
return a.exec();
}
執(zhí)行結(jié)果如下圖所示,用戶最先看到的是圖 3a),當(dāng)用戶鼠標(biāo)移動到文本框區(qū)域內(nèi)時,會提示“點(diǎn)擊超鏈接顯示URL”,提示時間為 1 秒。當(dāng)用戶點(diǎn)擊“老舅”時會觸發(fā) linkActivated() 信號函數(shù),該函數(shù)會調(diào)用 setText() 函數(shù),將文本框中顯示的“老舅”改為“http://www.baidu.net”,字體顏色為紅色,如圖 3b) 所示。
圖 3 程序運(yùn)行結(jié)果
有關(guān) QLabel 類提供的更多屬性和方法,后續(xù)章節(jié)用到時會做詳細(xì)地講解,您也可以借助 Qt Creator 提供的 Qt 幫助手冊自行查看 QLabel 類提供的成員。
按鈕是 GUI 開發(fā)中最常用到的一種控件,作為一款著名的 GUI 開發(fā)框架,Qt 提供了很多種按鈕,比如 QPushButton(普通按鈕)、QRadioButton(單選按鈕)、QToolButton(工具欄按鈕)等。
QPushButton 是實際開發(fā)中最常使用的一種按鈕,本節(jié)就給大家詳細(xì)講解它的用法。
QPushButton 類間接繼承自 QWidget 類,它的繼承關(guān)系如下:
QPushButton -> QAbstractButton -> QWidget
QAbstractButton 類是所有按鈕控件類的基類,包含很多通用的按鈕功能。
QPushButton 類專門用來創(chuàng)建可按壓的按鈕,如圖 1 所示。
圖 1 QPushButton 按鈕
QPushButton 按鈕上除了可以放置一串文本,文本左側(cè)還可以放置圖標(biāo),必要時還可以在按鈕上放置圖片。QPushButton 按鈕可以作為一個獨(dú)立的窗口,但實際開發(fā)中很少這樣用,通常的用法是像圖 1 這樣將按鈕內(nèi)嵌到某個窗口中,作為一個子控件和其它控件搭配使用。
QPushButton 類提供了 3 個構(gòu)造函數(shù),分別是:
QPushButton(QWidget *parent=Q_NULLPTR)
QPushButton(const QString &text, QWidget *parent=Q_NULLPTR)
QPushButton(const QIcon &icon, const QString &text, QWidget *parent=Q_NULLPTR)
parent 參數(shù)用于指定父窗口;text 參數(shù)用于設(shè)置按鈕上要顯示的文字;icon 參數(shù)用于設(shè)置按鈕上要顯示的圖標(biāo)。
注意,第一個構(gòu)造函數(shù)的 parent 參數(shù)附有默認(rèn)值,所以 QPushButton 類還隱含著一個默認(rèn)構(gòu)造函數(shù)。也就是說,實例化 QPushButton 類對象時可以不傳遞任何參數(shù)。
QPushButton 類提供了很多實用的屬性和方法,它還從父類繼承了很多屬性和方法。下表給大家羅列了一些比較常用的屬性和方法:
屬 性 | 含 義 |
text | 保存按鈕上要顯示的文字。 該屬性的值可以通過 text() 方法獲取,也可以通過 setText(const QString &text) 方法修改。 |
icon | 保存按鈕左側(cè)要顯示的圖標(biāo)。 該屬性的值可以通過 icon() 方法獲取,也可以通過 setIcon(const QIcon &icon) 方法修改。 |
iconsize | 保存按鈕左側(cè)圖標(biāo)的尺寸。 該屬性的值可以通過 iconSize() 方法獲取,也可以通過 setIconSize(const QSize &size) 方法修改。 |
size | 保存按鈕的尺寸。 該屬性的值可以通過 size() 方法獲取,也可以通過 resize(int w, int h) 或者 resize(const QSize &) 方法修改。 |
font | 保存按鈕上文字的字體和大小。 該屬性的值可以通過 font() 方法獲取,也可以通過 setFont(const QFont &) 方法修改。 |
flat | 初始狀態(tài)下,按鈕是否顯示邊框。flat 屬性的默認(rèn)值為 flase,表示按鈕帶有邊框。 該屬性的值可以通過 isFlat() 方法獲取,也可以通過 setFlat(bool) 方法修改。 |
enabled | 指定按鈕是否可以被按下。 該屬性的默認(rèn)值為 true,表示按鈕可以被按下,即按鈕處于啟用狀態(tài)。當(dāng)該屬性的值為 false 時,按鈕將不能被點(diǎn)擊,按鈕處于禁用狀態(tài)。 該屬性的值可以通過 isEnabled() 方法獲取,也可以通過 setEnabled(bool) 方法進(jìn)行修改。 |
autoDefault | 當(dāng)用戶按下 Enter 回車鍵時,是否觸發(fā)點(diǎn)擊按鈕的事件。 當(dāng)按鈕的父窗口為 QDialog 窗口時,該屬性的值為 true;其它情況下,該屬性的默認(rèn)值為 false。 該屬性的值可以通過 autoFault() 方法獲取,也可以通過 setAutoFault(bool) 方法修改。 |
除了表 1 中羅列的獲取、修改屬性值的方法外,QPushButton 類常用的成員方法還有:
方 法 | 功 能 |
move(int x, int y) | 手動指定按鈕位于父窗口中的位置。 |
setStyleSheet(const QString &styleSheet) | 自定義按鈕的樣式,包括按鈕上文字或圖片的顯示效果,按鈕的形狀等等。 |
setGeometry(int x, int y, int w, int h) | 同時指定按鈕的尺寸和位置。 |
adjustSize() | 根據(jù)按鈕上要顯示的內(nèi)容,自動調(diào)整按鈕的大小。 |
setDisabled(bool disable) | 指定按鈕是否可以被按下。當(dāng) disable 值為 true 時,表示按鈕不能被按下,即禁用按鈕的功能。 |
GUI 程序中,按鈕的主要任務(wù)是完成和用戶之間的交互,下表羅列了 QPushButton 類常用的信號函數(shù)和槽函數(shù):
信號函數(shù) | 功 能 |
clicked() clicked(bool checked=false) | 用戶點(diǎn)擊按鈕并釋放(或者按下按鈕對應(yīng)的快捷鍵)后,觸發(fā)此信號。 |
pressed() | 用戶按下按鈕時會觸發(fā)此信號。 |
released() | 用戶松開按鈕時會觸發(fā)此信號。 |
槽函數(shù) | 功 能 |
click() | 單擊指定的按鈕。 |
setIconSize() | 重新設(shè)置按鈕上圖片的尺寸。 |
hide() | 隱藏按鈕控件。 |
setMenu(QMenu *menu) | 彈出與按鈕關(guān)聯(lián)的菜單。 |
接下來通過一個實例,給大家演示 QPushButton 按鈕的用法:
#include <QApplication>
#include <QWidget>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget widget;
//設(shè)置 widget 窗口的標(biāo)題
widget.setWindowTitle("QWidget窗口");
//創(chuàng)建一個按鈕,并內(nèi)嵌到 widget 窗口中
QPushButton but("QPushButton按鈕",&widget);
//按鈕的位置位于距 widget 窗口左上角 (100,100) 的位置
but.move(100,100);
//設(shè)置按鈕上文字的大小。
but.setStyleSheet("QPushButton{font:20px;}");
//調(diào)整按鈕的尺寸
but.resize(200,200);
//建立信息和槽,當(dāng)用戶點(diǎn)擊并釋放按鈕后,該按鈕隱藏。
QObject::connect(&but,&QPushButton::clicked,&but,&QPushButton::hide);
widget.show();
return a.exec();
}
將程序復(fù)制到 main.cpp 文件中,運(yùn)行結(jié)果為:
圖 2 運(yùn)行結(jié)果
QLineEdit 是 Qt 提供的一個控件類,它直接繼承自 QWdiget 類,專門用來創(chuàng)建單行輸入框,如下圖所示:
圖 1 單行文本輸入框
實際開發(fā)中,我們經(jīng)常用到 QLineEdit 輸入框,比如接收用戶輸入的個人信息、賬戶信息、角色名稱等,就可以用 QLineEdit 實現(xiàn)。
【文章福利】:Qt開發(fā)學(xué)習(xí)資料包、Qt面試題文檔、項目視頻、學(xué)習(xí)路線,包括(Qt C++基礎(chǔ),數(shù)據(jù)庫編程,Qt項目實戰(zhàn)、Qt框架、QML、Opencv、qt線程等等),免費(fèi)分享,有需要的可以加君羊領(lǐng)取哦!~學(xué)習(xí)交流君羊937552610點(diǎn)擊加入領(lǐng)取資料
每個單行輸入框都是 QLineEdit 類的一個實例對象,QLineEdit 類提供有兩個構(gòu)造函數(shù),分別是:
QLineEdit(QWidget *parent=Q_NULLPTR)
QLineEdit(const QString &contents, QWidget *parent=Q_NULLPTR)
contents 參數(shù)用于指定輸入框中的文本內(nèi)容;parent 參數(shù)用于指定新建輸入框控件的父窗口,新建輸入框?qū)?nèi)嵌到父窗口上,作為父窗口的一個子控件。當(dāng)然,我們也可以不指定父窗口,那么新建的輸入框就會作為獨(dú)立的窗口。
在 QLineEdit 輸入框中,用戶可以直接輸入一行文本,也可以粘貼一行文本,還可以修改輸入框內(nèi)的文本。某些實際場景中,QLineEdit 輸入框還可以對用戶輸入的內(nèi)容加以限定,比如:
限定文本的長度,例如用戶最多可以輸入 20 個字符;
輸入文本的格式,例如用戶輸入出生日期時,必須按照“yy-mm-dd”的格式輸入;
輸入的文本內(nèi)容,例如當(dāng)前輸入框僅允許用戶輸入數(shù)字,或者只允許用戶輸入英文字符。
QLineEdit 類的內(nèi)部提供了很多實用的屬性和方法,同時還從 QWidget 父類處繼承了一些屬性和方法。
下表列出了 QLineEdit 類對象經(jīng)常調(diào)用的一些屬性以及它們各自的含義:
屬 性 | 含 義 |
text | 保存輸入框中的文本。 該屬性的值可以通過 text() 方法獲取,也可以通過 setText(const QString &) 方法修改。 |
maxLength | 設(shè)置輸入框中最多可以放置的文本長度。當(dāng)文本長度超出最大限度后,超出部分將被丟棄。 默認(rèn)情況下,maxLength 的值為 32767。該屬性的值可以通過 maxLength() 函數(shù)獲得,也可以通過 setMaxLength(int) 方法修改。 |
placeholderText | 設(shè)置提示信息,例如當(dāng)用戶未選中輸入框時,輸入框中顯示“請輸入…”,而用戶選中輸入框時,“請輸入…” 隨之消失。 該屬性的值可以通過 placeholderText() 方法獲取,也可以通過 setPlaceholderText(const QString &) 方法修改。 |
clearButtonEnabled | 當(dāng)輸入框中有文本時,輸入框的右側(cè)可以顯示一個“一鍵清除”按鈕。該屬性的默認(rèn)值為 false,即輸入框中不會自動顯示清除按鈕。 該屬性的值可以通過 isClearButtonEnabled() 方法獲取,也可以通過 setClearButtonEnabled(bool enable) 方法修改。 |
echoMode | 設(shè)定輸入框中文本的顯示樣式,該屬性的可選值有以下幾個:QLineEdit::Normal:正常顯示所輸入的字符,此為默認(rèn)選項。QLineEdit::NoEcho:不顯示任何輸入的字符,常用于密碼類型的輸入,且長度保密QLineEdit::Password:顯示與平臺相關(guān)的密碼掩飾字符,而不是實際輸入的字符。當(dāng)用戶重新點(diǎn)擊輸入框時,可以緊接著之前的文本繼續(xù)輸入。QLineEdit::PasswordEchoOnEdit:編輯時正常顯示輸入的字符,編輯完成后改為用密碼掩飾字符顯示。當(dāng)用戶重新點(diǎn)擊輸入框時,不能緊接著之前的文本繼續(xù)輸入。 該屬性的是可以通過 echoMode() 方法獲取,也可以通過 setEchoMode(EchoMode) 方法修改。 |
frame | 控制輸入框的邊框。默認(rèn)情況下,輸入框是帶有邊框的。 該屬性的值可以通過 hasFrame() 方法獲取,也可以通過 setFrame(bool) 方法修改。 |
除了上表提到的獲取和修改屬性值的方法外,QLineEdit 類還提供了一些功能實用的方法,例如:
成員方法 | 功 能 |
move(int x, int y) | 指定輸入框位于父窗口中的位置。 |
setValidator(const QValidator *v) | 限制輸入框中的文本內(nèi)容,比如輸入框只包含整數(shù)。 |
setReadOnly(bool) | 設(shè)置輸入框是否進(jìn)入只讀狀態(tài)。在只讀狀態(tài)下,用戶仍可以采用粘貼、拖拽的方式向輸入框中放置文本,但無法進(jìn)行編輯。 |
setAlignent(Qt::Alignment flag) | 設(shè)置輸入框中輸入文本的位置。 |
QLineEdit 類提供了幾個信號函數(shù),分別對應(yīng)用戶的幾種輸入狀態(tài)。
信號函數(shù) | 功 能 |
textEdited(const QString &text) | 當(dāng)用戶編輯輸入框中的文本時,此信號就會觸發(fā),text 參數(shù)即為用戶新編輯的文本。 注意,當(dāng)程序中試圖通過 setText() 方法修改輸入框中的文本時,不會觸發(fā)此信號函數(shù)。 |
textChanged(const QString &text) | 只要輸入框中的文本內(nèi)容發(fā)生變化,就會觸發(fā)此信息。 |
returnPressed() | 用戶按下回車鍵時,會觸發(fā)此信號。 |
editingFinished() | 用戶按下回車鍵,或者鼠標(biāo)點(diǎn)擊輸入框外的其它位置時,會觸發(fā)此信號。 |
QLineEdit 類常用的槽函數(shù)有以下幾個:
槽函數(shù) | 功 能 |
clear() | 清空文本框中的內(nèi)容。 |
setText(const QString &) | 重新指定文本框中的內(nèi)容。 |
下面的實例給大家演示了 QLineEdit 單行輸入框控件的基本用法,同時還演示了幾個成員方法的用法。
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
using namespace std;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建一個窗口,作為輸入框的父窗口
QWidget widget;
//設(shè)置窗口的標(biāo)題
widget.setWindowTitle("QWidget窗口");
//接下來,分別創(chuàng)建兩個輸入框,用于讓用戶分別輸入賬號和密碼
//創(chuàng)建賬號輸入框
QLineEdit lineEdit(&widget);
//指定輸入框位于父窗口中的位置
lineEdit.move(100,100);
//設(shè)置提示信息
lineEdit.setPlaceholderText("請輸入賬號...");
//讓輸入框顯示“一鍵清除”按鈕
lineEdit.setClearButtonEnabled(true);
//創(chuàng)建密碼輸入框
QLineEdit lineEditPass(&widget);
lineEditPass.setPlaceholderText("請輸入密碼...");
lineEditPass.move(100,150);
//指定文本顯示方式,保護(hù)用戶賬號安全
lineEditPass.setEchoMode(QLineEdit::Password);
//指定窗口的尺寸和顯示文字的大小
widget.resize(500,300);
widget.setFont(QFont("宋體",16));
widget.show();
return a.exec();
}
運(yùn)行結(jié)果為:
圖 2 運(yùn)行結(jié)果
很多應(yīng)用程序中需要以列表的形式向用戶展示數(shù)據(jù)(資源),比如 Windows 操作系統(tǒng)會以列表的方式展示很多張桌面背景圖(如圖 1a) 所示),再比如很多音樂播放器中以列表的形式展示音樂資源,用戶可以選擇自己喜歡的音樂(如圖 1b) 所示)。
圖 1 常見的列表窗口
使用 Qt 框架開發(fā) GUI 程序,如果需要以列表的方法展示數(shù)據(jù),可以優(yōu)先考慮用 QListWidget 類實現(xiàn)。
QListWidget 是 Qt 提供的控件類,專門用來創(chuàng)建列表。QListWidget 類的繼承關(guān)系如下:
QListWidget -> QListView -> QAbstractItemView -> QAbstractScrollArea -> QFrame -> QWidget
這里著重介紹一下 QListView 類,它也可以用來創(chuàng)建列表。對于初學(xué)者來說,我強(qiáng)烈建議先學(xué)習(xí) QListWidget,它是“簡易版”的 QListView,創(chuàng)建和使用列表的方式更簡單、門檻更低,對初學(xué)者更友好。當(dāng)然,QListWidget 只能創(chuàng)建結(jié)構(gòu)簡單的列表,如果要制作復(fù)雜的列表,應(yīng)優(yōu)先考慮 QListView,因為它的功能更強(qiáng)大,很多 QListWidget 難以實現(xiàn)的功能,QListView 都能實現(xiàn)。
通過實例化 QListWidget 類,可以很輕松地創(chuàng)建一個列表。QListWidget 類只提供了 1 個構(gòu)造函數(shù):
QListWidget(QWidget *parent=Q_NULLPTR)
parent 參數(shù)用來指定新建列表的父窗口,該參數(shù)的默認(rèn)值是 Q_NULLPTR,表示新建控件沒有父窗口。
語法層面上分析,可以不為 QListWidget 列表指定父窗口,那么它將作為一個獨(dú)立的窗口。但實際開發(fā)中,通常會為 QListWidget 列表指定一個父窗口(例如 QWidget 窗口),它將作為父窗口中的一個子控件,和窗口中的其它控件一起搭配使用。
QListWidgetItem列表項
QListWidget 列表控件可以顯示多份數(shù)據(jù),每份數(shù)據(jù)習(xí)慣稱為列表項(簡稱項),每個列表項都是 QListWidgetItem 類的實例對象。也就是說,QListWidget 中有多少個列表項,就有多少個 QListWidgetItem 類對象。
默認(rèn)情況下,QListWidget 中每個列表項獨(dú)自占用一行,每個列表項中可以包含文字、圖標(biāo)等內(nèi)容。實際開發(fā)中,我們還可以將指定的窗口或者控件放置到列表項中顯示,例如 QWidget 窗口、QLabel 文本框、QPushButton 按鈕、QLineEdit 輸入框等。
借助 QListWidgetItem 類,可以輕松管理 QListWidget 中的每個列表項,包括:
借助 QListWidgetItemo 類提供的 setIcon()、setText() 等方法,可以輕松地指定每個列表項要包含的內(nèi)容;
借助 QListWidgetItemo 類提供的 setFont()、setBackground() 等方法,可以輕松地設(shè)置每個列表項的外觀(文字大小、列表項背景等)。
當(dāng)然,QListWidgetItem 類還提供有很多其它的成員方法,這里不再一一羅列。
對于剛剛創(chuàng)建好的 QListWidget 類對象,不包含任何 QListWidgetItem 類對象,就是一個空列表。借助 QListWidget 類以及父類提供的屬性和方法,我們可以對新建列表執(zhí)行多種操作。
下表給大家羅列了一些 QListWidget 類常用的屬性和方法:
屬性名或方法名 | 功 能(含 義) |
count 屬性 | 保存當(dāng)前列表中含有的列表項的總數(shù)。 該屬性的值可以通過 count() 方法獲取。 |
currentRow 屬性 | 保存當(dāng)前選擇的列表項所在的行數(shù)。 該屬性的值可以通過 currentRow() 方法獲取,也可以通過 setCurrentRow(int row) 方法修改當(dāng)前選擇的列表項。 |
sortingEnabled 屬性 | 決定當(dāng)前 QlistWidget 列表是否開發(fā)排序功能,默認(rèn)值為 false,即不開啟排序功能。 該屬性的是可以通過 isSortingEnabled() 方法獲取,可以通過 setSortingEnabled(bool enable) 方法進(jìn)行修改。 |
SelectionMode 屬性 | 指明當(dāng)前列表中是否可以同時選擇多個列表項,或者是否只能連續(xù)選擇多個列表項。 該屬性是枚舉類型,可選值有 5 個:QAbstractItemView::SingleSelection:用戶最多只能選擇一個列表項。QAbstractItemView::ContiguousSelection:用戶按住 Shift 鍵,可以連續(xù)選擇多個列表項。QAbstractItemView::ExtendedSelection:用戶按住 Ctrl 鍵,可以選中多個列表項。當(dāng)用戶按住 Shift 鍵,也可以連續(xù)選擇多個列表項。QAbstractItemView::MultiSelection:用戶可以選擇多個列表項。QAbstractItemView::NoSelection:用戶無法選擇任何列表項。 該屬性的值可以通過 selectionMode() 方法獲取,也可以通過 setSelectionMode(QAbstractItemView::SelectionMode mode) 方法進(jìn)行修改。 |
ViewMode 屬性 | 指定 QListWidget 是按行顯示數(shù)據(jù)(如圖 1b) ),還是分列顯示數(shù)據(jù)(如圖 1a) )。 該屬性是枚舉類型,可選值有 2 個:QListView::ListMode:一行一行地顯示列表項,默認(rèn)情況下,各個列表項不能拖動。QListView::IconMode:分列顯示列表項,默認(rèn)情況下,各個列表項可以拖動。 該屬性的值可以通過 viewMode() 方法獲取,也可以通過 setViewMode(ViewMode mode) 方法進(jìn)行修改。 |
void addItem ( const QString & label ) void addItem ( QListWidgetItem * item ) void addItems ( const QStringList & labels ) | 向 QListWidget 列表的尾部添加指定項,可以是一個文本(label)、一個列表項(item),還可以一次性添加多個文本(labels)。 |
void QListWidget::setItemWidget(QListWidgetItem *item, QWidget *widget) | 將指定的 widget 窗口添加到 item 列表項中。 |
currentItem() | 返回當(dāng)前選中的列表項。 |
removeItemWidget(QListWidgetItem *item) | 刪除指定的 item 列表項。 |
sortItems(Qt::SortOrder order=Qt::AscendingOrder) | 默認(rèn)將所有列表項按照升序排序,通過指定參數(shù)為 Qt::DescendingOrder,可以進(jìn)行降序排序。 |
takeItem(int row) | 返回位于 row 行的列表項。 |
selectedItems() | 返回當(dāng)前被選擇的所有列表項。 |
對于給定的 QlistWidget 列表,用戶可以選中其中的一個或者某些列表項,甚至還可以修改列表項中的內(nèi)容。QListWidget 類具有很多信號和槽信息,可以捕捉用戶的很多動作,還可以針對用戶的動作做出適當(dāng)?shù)仨憫?yīng)。
下表給大家羅列了一些常用的信號和槽函數(shù):
信號函數(shù) | 功 能 |
itemClicked(QListWidgetItem *item) | 用戶點(diǎn)擊某個列表項時會觸發(fā)此信號函數(shù),item 參數(shù)指的就是被用戶點(diǎn)擊的列表項。 |
itemDoubleClicked(QListWidgetItem *item) | 用戶雙擊某個列表項時會觸發(fā)此信號函數(shù),item 參數(shù)指的就是被用戶雙擊的列表項。 |
itemPressed(QListWidgetItem *item) | 鼠標(biāo)按下某個列表項時會觸發(fā)此信號函數(shù),item 參數(shù)指的就是被鼠標(biāo)按下的列表項, |
itemSelectionChanged() | 當(dāng)選擇的列表項發(fā)生變化時會觸發(fā)此信號函數(shù)。 |
currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) | 當(dāng)前列表項發(fā)生變化時會觸發(fā)此信號函數(shù),current 參數(shù)指的是新選擇的列表項;previous 參數(shù)指的是先前選擇的列表項。 |
槽函數(shù) | 功 能 |
clear() | 刪除列表中的所有列表項。 |
scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint=EnsureVisible) | 用 hint 參數(shù)指定的滑動方式,讓用戶看到指定的 item 列表項。 |
selectAll() | 選擇所有的列表項。 |
scrollToBottom() scrollToTop() | 分別將列表滑動到底部和頂部。 |
接下來,我們親自制作一個 QListWidget 列表:
//main.cpp#include <QApplication>#include <QWidget>#include <QListWidget>#include <QLabel>#include <QListWidgetItem>using //main.cpp
#include <QApplication>
#include <QWidget>
#include <QListWidget>
#include <QLabel>
#include <QListWidgetItem>
using namespace std;
class QMyLabel:public QLabel{
Q_OBJECT
public slots:
void rsetText(QListWidgetItem *item);
};
void QMyLabel::rsetText(QListWidgetItem *item){
this->setText(item->text());
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建一個窗口,作為輸入框的父窗口
QWidget widget;
//設(shè)置窗口的標(biāo)題
widget.setWindowTitle("QWidget窗口");
widget.resize(500,500);
QListWidget listWidget(&widget);
listWidget.resize(500,400);
listWidget.setFont(QFont("宋體",14));
listWidget.addItem("老舅");
listWidget.addItem("http://www.baidu.net");
listWidget.addItem(new QListWidgetItem("Qt教程"));
QMyLabel print;
print.setText("選中內(nèi)容");
print.setParent(&widget);
print.resize(500,100);
print.move(0,400);
print.setAlignment(Qt::AlignCenter);
QObject::connect(&listWidget,&QListWidget::itemClicked,&print,&QMyLabel::rsetText);
widget.show();
return a.exec();
}
//QMyLabel類的定義應(yīng)該放到 .h 文件中,本例中將其寫到 main.cpp 中,程序最后需要添加 #include "當(dāng)前源文件名.moc" 語句,否則無法通過編譯。
#include "main.moc"
程序中,我們自定義了一個 QMyLabel 類,它繼承自 QLabel 文本框類,因此 QMyLabel 也是一個文本框類。在 QMyLabel 類中,我們自定義了一個 rsetText() 槽函數(shù)。
程序的運(yùn)行結(jié)果為:
圖 2 QListMidget 控件實際應(yīng)用
通過運(yùn)行動畫可以看到,我們將 QListMidget 和自定義的 QMyLabel 控件合理地分布在 QWidget 窗口上,通過為它們的信號和槽建立連接,使得當(dāng)點(diǎn)擊列表中的某個列表項時,文本框可以顯示列表項中的文本內(nèi)容。
實際開發(fā)中,一個界面上可能包含十幾個控件,手動調(diào)整它們的位置既費(fèi)時又費(fèi)力。作為一款成熟的 GUI 框架,Qt 提供了很多擺放控件的輔助工具(又稱布局管理器或者布局控件),它們可以完成兩件事:
自動調(diào)整控件的位置,包括控件之間的間距、對齊等;
當(dāng)用戶調(diào)整窗口大小時,位于布局管理器內(nèi)的控件也會隨之調(diào)整大小,從而保持整個界面的美觀。
總之借助布局管理器,我們無需再逐個調(diào)整控件的位置和大小,可以將更多的精力放在軟件功能的實現(xiàn)上。
Qt 共提供了 5 種布局管理器,每種布局管理器對應(yīng)一個類,分別是 QVBoxLayout(垂直布局)、QHBoxLayout(水平布局)、QGridLayout(網(wǎng)格布局)、QFormLayout(表單布局)和 QStackedLayout(分組布局),它們的繼承關(guān)系如下圖所示:
圖 1 各個布局管理類的繼承關(guān)系
垂直布局指的是將所有控件從上到下(或者從下到上)依次擺放,例如:
圖 2 QVBoxLayout垂直布局
圖 2 展示了 4 個 QPushButton 按鈕利用 QVBoxLayout 垂直布局的效果。實際場景中,QVBoxLayout 中還可以放置其它控件,比如 QLabel 文本框、QLineEdit 單行輸入框等。
程序中使用 QVBoxLayout 布局控件,需提前引入<QVBoxLayout>頭文件。每個 QVBoxLayout 控件本質(zhì)都是 QVBoxLayout 類的實例對象,該類提供了兩個構(gòu)造函數(shù),分別是:
QVBoxLayout()
QVBoxLayout(QWidget *parent)
創(chuàng)建 QVBoxLayout 控件的同時可以指定父窗口,那么它將作為父窗口中管理其它控件的工具;也可以暫時不指定父窗口,待全部設(shè)置完畢后再將其添加到某個窗口中。
QVBoxLayout 類沒有新增任何成員方法,它只能使用從父類繼承的成員方法,下表給大家羅列了常用的一些:
成員方法 | 功 能 |
void QBoxLayout::addWidget(QWidget *widget, int stretch=0, Qt::Alignment alignment=Qt::Alignment()) | 向布局管理器中添加指定的 widget 控件。 默認(rèn)情況下,stretch 拉伸系數(shù)為 0,表示 widget 控件的尺寸為默認(rèn)值;alignment 是一個枚舉類型參數(shù),默認(rèn)值也是 0,表示該控件會填滿占用的整個空間。 |
void QBoxLayout::addStretch(int stretch=0) | 添加一個空白行,整個窗口中除了控件占用的區(qū)域外,其它區(qū)域可以由多個(≥0)空白行分?jǐn)偅謹(jǐn)偙壤∮嘤诟鱾€空白行設(shè)置的 stretch 參數(shù)的值。 strech 參數(shù)的默認(rèn)值為 0,表示當(dāng)窗口很小時,空白行可以不占據(jù)窗口空間。當(dāng)窗口中包含多個 strech 值為 0 的空白行時,它們會平分窗口中的空白區(qū)域。 |
void QBoxLayout::addSpacing(int size) | 添加一個 size 大小的固定間距。 |
void QLayout::setMargin(int margin) | 設(shè)置布局管理器中所有控件的外邊距,上、下、左、右外邊距的大小都為 margin。默認(rèn)情況下,所有方向的外邊距為 11 px。 |
void QLayout::setContentsMargins(int left, int top, int right, int bottom) | 設(shè)置布局管理器中所有控件的外邊距,和 setMargin() 的區(qū)別是,此方法可以自定義上、下、左、右外邊距的值。 |
void QBoxLayout::setDirection(Direction direction) | 設(shè)置布局管理器中控件的布局方向,Direction 是一個枚舉類型,對于 QVBoxLayout 布局管理器,direction 參數(shù)的值通常選擇 QBoxLayout::TopToBottom(從上到下依次擺放)或者 QBoxLayout::BottomToTop(從下到上依次擺放)。 |
bool QBoxLayout::setStretchFactor(QWidget *widget, int stretch) | 設(shè)置布局管理器中某個控件的拉伸系數(shù)。 |
bool QBoxLayout::setStretchFactor(QLayout *layout, int stretch) | 布局管理器內(nèi)部可以再放置一個布局管理器,該方法用來設(shè)置內(nèi)部某個布局管理器的拉伸系數(shù)。 |
舉個簡單的例子:
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建主窗口
QWidget widget;
widget.setWindowTitle("QVBoxLayout垂直布局");
//創(chuàng)建垂直布局管理器
QVBoxLayout *layout=new QVBoxLayout;
//設(shè)置布局管理器中所有控件從下往上依次排列
layout->setDirection(QBoxLayout::BottomToTop);
//連續(xù)創(chuàng)建 3 個文本框,并設(shè)置它們的背景和字體大小
QLabel lab1("Label1");
lab1.setStyleSheet("QLabel{background:#dddddd;font:20px;}");
lab1.setAlignment(Qt::AlignCenter);
QLabel lab2("Label2");
lab2.setStyleSheet("QLabel{background:#cccccc;font:20px;}");
lab2.setAlignment(Qt::AlignCenter);
QLabel lab3("Label3");
lab3.setStyleSheet("QLabel{background:#ffffff;font:20px;}");
lab3.setAlignment(Qt::AlignCenter);
//將 3 個文本框和 2 個空白行添加到管理器中,它們的伸縮系數(shù)比是 2:1:2:3:3
layout->addStretch(2);
layout->addWidget(&lab1,1);
layout->addWidget(&lab2,2);
layout->addWidget(&lab3,3);
layout->addStretch(3);
//將布局管理器添加到 widget 窗口中
widget.setLayout(layout);
widget.show();
return a.exec();
}
程序中做了以下幾個操作:
通過調(diào)用 setDirection() 方法,將添加到 QVBoxLayout 管理器中的所有控件(包括空白行)按照從下到上的順序依次擺放。舉個例子,由于 lab1 文本框是第二個添加到管理器中的,因此在最終顯示的界面中,lab1 位于倒數(shù)第二的位置。
通過調(diào)用 addStrech() 方法,向管理器中先后添加了兩個空白行,它們的伸縮系數(shù)分別為 2 和 3,因此 widget 窗口中的空白區(qū)域會平均分為 5 份,一個空白行占 3 份,另一個占 2 份。
通過調(diào)用 addWidget() 方法,向管理器中先后添加了 3 個文本框,它們的拉伸系數(shù)比為 1:2:3,所以當(dāng)我們拉伸 widget 窗口時,三個文本框的大小(寬度)呈現(xiàn) 1:2:3 的關(guān)系。
通過調(diào)用 setLayout() 方法,成功地將 layout 布局管理器添加到了 widget 窗口中。當(dāng)然,也可以在創(chuàng)建 layout 對象時指定 widget 作為它的父窗口,兩種方式是完全等價的。
執(zhí)行結(jié)果為:
圖 3 QVBoxLayout 實例演示
水平布局指的是將所有控件從左到右(或者從右到左)依次擺放,例如:
圖 4 QHBoxLayout水平布局
使用 QHBoxLayout 水平布局控件,程序中要提前引入<QHBoxLayout>頭文件。QHBoxLayout 和 QVBoxLayout 繼承自同一個類,它們的用法非常相似,比如 QHBoxLayout 類也提供了兩個構(gòu)造函數(shù):
QHBoxLayout()
QHBoxLayout(QWidget *parent)
QHBoxLayout 類也沒有新添任何成員方法,它只能使用從父類繼承的成員方法。因此,表 1 中羅列的所有成員方法也同樣適用于 QHBoxLayout 對象。
注意,當(dāng) QHBoxLayout 對象調(diào)用表 1 中的 addStretch() 方法時,表示添加一個空白列。
舉個簡單的例子:
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QHBoxLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建主窗口
QWidget widget;
widget.setWindowTitle("QHBoxLayout水平布局");
//創(chuàng)建水平布局管理器
QHBoxLayout *layout=new QHBoxLayout;
//設(shè)置布局管理器中所有控件的布局方向為從右往左依次排列
layout->setDirection(QBoxLayout::RightToLeft);
//連續(xù)創(chuàng)建 3 個文本框,并設(shè)置它們的背景和字體大小
QLabel lab1("Label1");
lab1.setStyleSheet("QLabel{background:#dddddd;font:20px;}");
lab1.setAlignment(Qt::AlignCenter);
QLabel lab2("Label2");
lab2.setStyleSheet("QLabel{background:#cccccc;font:20px;}");
lab2.setAlignment(Qt::AlignCenter);
QLabel lab3("Label3");
lab3.setStyleSheet("QLabel{background:#ffffff;font:20px;}");
lab3.setAlignment(Qt::AlignCenter);
//將 3 個文本框和 2 個空白列添加到管理器中,它們的拉伸系數(shù)比是 2:1:2:3:3
layout->addStretch(2);
layout->addWidget(&lab1,1);
layout->addWidget(&lab2,2);
layout->addWidget(&lab3,3);
layout->addStretch(3);
//將布局管理器添加到 widget 窗口中
widget.setLayout(layout);
widget.show();
return a.exec();
}
程序執(zhí)行結(jié)果為:
圖 5 QHBoxLayout水平布局實例
圖 5 中,最左側(cè)和最右側(cè)各添加了一個空白列,它們的伸縮比例為 3:2,即它們的寬度比為 3:2。
網(wǎng)格布局又稱格柵布局或者表格布局,指的是將一些控件按照行和列排列在窗口上,例如:
圖 6 QGridLayout網(wǎng)格布局
QGridLayout 的行標(biāo)和列標(biāo)都從 0 開始,例如圖 6 中 one 按鈕的位置為 (0, 0),F(xiàn)our 按鈕的位置為 (2, 0)。我們可以隨意指定 QGridLayout 的行數(shù)和列數(shù),各個控件可以隨意擺放,必要時某些位置可以空著不用。
使用 QGridLayout 網(wǎng)格控件,程序中需引入<QGridLayout>頭文件。每個 QGridLayout 控件都是 QGridLayout 類的一個實例對象,該類提供了兩個構(gòu)造函數(shù),分別是:
QGridLayout(QWidget *parent)
QGridLayout()
QGridLayout 類提供了很多實用的成員方法,常用的如下表所示:
成員方法 | 功 能 |
int QGridLayout::rowCount() const | 獲取網(wǎng)格的行數(shù)。 |
int QGridLayout::columnCount() const | 獲取網(wǎng)格的列數(shù)。 |
void QGridLayout::addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment=Qt::Alignment()) | 將 widget 控件添加到網(wǎng)格中的 (row,column) 位置處,并且可以自定義該控件的對齊方式。 |
void QGridLayout::addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment=Qt::Alignment()) | 將 widget 控件從 (fromRow, fromColumn) 位置開始,跨 rowSpan 行和 ColumnSpan 列添加到網(wǎng)格中,并且可以自定義該控件的對齊方式。 |
void QGridLayout::addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment=Qt::Alignment()) | 向網(wǎng)格中的 (row, column) 位置處添加 layout 布局管理器。 |
void QGridLayout::addLayout(QLayout *layout, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment=Qt::Alignment()) | 將 layout 布局管理器從 (row, column) 位置開始,跨 rowSpan 行和 ColumnSpan 列添加到網(wǎng)格中,并且可以自定義該布局控件的對齊方式。 |
void QGridLayout::setColumnStretch(int column, int stretch) | 給指定的第 column 列設(shè)置伸縮系數(shù)。 |
void QGridLayout::setRowStretch(int row, int stretch) | 給指定的第 row 行設(shè)置伸縮系數(shù)。 |
void QGridLayout::setColumnMinimumWidth(int column, int minSize) | 設(shè)置第 column 列的最小寬度。 |
void QGridLayout::setRowMinimumHeight(int row, int minSize) | 設(shè)置第 row 行的最小寬度。 |
舉個簡單的例子:
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QGridLayout>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建主窗口
QWidget widget;
widget.setWindowTitle("QGridLayout網(wǎng)格布局");
//創(chuàng)建 4 個按鈕和 1 個文本框
QPushButton *but1=new QPushButton("but1");
QPushButton *but2=new QPushButton("but2");
QLabel *lab3=new QLabel("lab");
lab3->setStyleSheet("QLabel{background:#dddddd;font:20px;}");
lab3->setAlignment(Qt::AlignCenter);
QPushButton *but3=new QPushButton("but3");
QPushButton *but4=new QPushButton("but4");
//創(chuàng)建網(wǎng)格布局控件
QGridLayout *layout=new QGridLayout;
//向 layout 中添加控件,并指定各個控件的位置
layout->addWidget(but1, 0, 0);
layout->addWidget(but2, 0, 2);
layout->addWidget(lab3, 1, 0, 3, 3);
layout->addWidget(but3, 4, 0);
layout->addWidget(but4, 4, 2);
//將 layout 添加到 widget 窗口中
widget.setLayout(layout);
widget.show();
return a.exec();
}
程序運(yùn)行結(jié)果為:
圖 7 QGridLayout網(wǎng)格布局實例
圖 7 中,文本框控件從 (1,0) 位置開始,占據(jù)了 3 行 3 列的表格空間。
Qt 提供了很多種輸入框控件,包括 QLineEdit 單行輸入框、QTextEdit 多行輸入框等。通常情況下,每個輸入框的旁邊都會附帶一些文字(又稱標(biāo)簽),用來提示用戶需要輸入的信息。例如,圖 8 中第一個輸入框的標(biāo)簽為 “Name”,提示用戶填寫自己的姓名。
圖 8 QFromLayout表單布局
生成圖 8 這樣的界面,實現(xiàn)的方法有很多,例如:
分別創(chuàng)建 3 個 QLabel 控件和 3 個 QLineEdit 控件,手動指定它們的位置;
在 QHBoxLayout 中嵌套 3 個 QVBoxLayout,又或者在 QVBoxLayout 中嵌套 3 個 QHBoxLayout,然后再添加 3 個 QLabel 控件和 3 個 QLineEdit 控件;
使用 QGridLayout 創(chuàng)建一個 3 行 2 列的表格,向表格中添加 3 個 QLabel 控件和 3 個 QLineEdit 控件。
使用 QFormLayout 表單布局控件實現(xiàn)。
第 1 種方法最大的弊端在于,各個控件的尺寸都是固定的,不會隨著父窗口尺寸的改變而改變。第 2、3、4 種方法都是借助布局控件實現(xiàn)的,各個控件的尺寸可以自動調(diào)整,但前兩種方法需要手動設(shè)置每一列的 strech 拉伸系數(shù),而第 4 種方式不需要。總之對于生成類似圖 8 這樣的表單窗口,建議大家使用 QFormLayout 控件,因為使用 QFormLayout 編寫的代碼量最少,開發(fā)效率最高。
QFormLayout 可以容納很多個輸入框以及對應(yīng)的標(biāo)簽,并將它們從上到下依次排列在界面上(如圖 8 所示)。大多數(shù)情況下,QFormLayout 底層是用 QGridLayout 網(wǎng)格布局管理器實現(xiàn)的,和后者不同的是,QFormLayout 只包含 2 列(不限制行數(shù)),且第一列放置標(biāo)簽,第二列放置輸入框。
使用 QFormLayout 布局控件之前,程序中應(yīng)引入<QFormLayout>頭文件。每一個表單布局控件都是 QFormLayout 類的一個實例對象,該類僅提供了一個構(gòu)造函數(shù):
QFormLayout(QWidget *parent=Q_NULLPTR)
下表給大家羅列了操作 QFormLayout 對象常用的一些成員方法:
成員方法 | 功 能 |
void QFormLayout::addRow(QWidget *label, QWidget *field) | 將指定的 field 控件和存儲標(biāo)簽的 label 控件添加到表單控件中的末尾。 |
void QFormLayout::addRow(const QString &labelText, QWidget *field) | 將指定的 field 控件和 labelText 標(biāo)簽添加到表單控件的末尾。 |
void QFormLayout::insertRow(int row, const QString &labelText, QWidget *field) | 將指定的 field 控件和 labelText 標(biāo)簽插入到表單控件中指定行的位置。 |
void QFormLayout::removeRow(int row) | 刪除表單控件中的指定行。 |
void QFormLayout::removeRow(QWidget *widget) | 刪除表單控件中 widget 控件所在的行。 |
void setRowWrapPolicy(RowWrapPolicy policy) | 設(shè)置標(biāo)簽的顯示格式,默認(rèn)標(biāo)簽位于控件的左側(cè)。 RowWrapPolicy 是 QFormLayout 中定義的枚舉類型,該類型包含 3 個值:QFormLayout::DontWrapRows:標(biāo)簽始終在輸入框的左側(cè);QFormLayout::WrapLongRows:根據(jù)輸入框的尺寸,標(biāo)簽可能位于輸入框的左側(cè),也可能位于上方;QFormLayout::WrapAllRows:標(biāo)簽始終在輸入框的上方; |
void QFormLayout::setSpacing(int spacing) | 將行間距和列間距設(shè)置為 spacing。 |
舉個簡單的例子:
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QFormLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建主窗口
QWidget widget;
widget.setWindowTitle("QFormLayout表單布局");
//創(chuàng)建 4 個按鈕和 1 個文本框
QFormLayout* layout=new QFormLayout();
//設(shè)置表單中的標(biāo)簽都位于控件的上方
layout->setRowWrapPolicy(QFormLayout::WrapAllRows);
//添加 3 行輸入框和標(biāo)簽
layout->addRow("Name:",new QLineEdit());
layout->addRow("Email:",new QLineEdit());
layout->addRow("Adress:",new QLineEdit());
//設(shè)置行間距和列間距為 10
layout->setSpacing(10);
//將 layout 表單添加到 widget 窗口中
widget.setLayout(layout);
widget.show();
return a.exec();
}
程序運(yùn)行結(jié)果為:
圖 9 QFormLayout表單布局實例
QStackedLayout 布局管理器可以容納多個控件或者窗口,但每次只顯示其中的一個。
舉個簡單的例子,下圖中的界面就使用了 QStackedLayout 布局管理器:
圖 10 QStackedLayout布局管理器
整個窗口被一分為二,左側(cè)是 QListWidget 列表控件,右側(cè)是 QStackedLayout 布局管理器。QStackedLayout 中包含 QPushButonn、QLabel 和 QLineEdit 這 3 個控件,但每次只能 3 個控件中的一個。
QStackedLayout 自身無法切換當(dāng)前顯示的控件或窗口,實際應(yīng)用時通常和 QListWidget 或者 QComboBox 搭配使用。
使用 QStackedLayout 布局控件,程序中必須先引入<QStackedLayout>頭文件。 每個 QStackedLayout 控件都是 QStackedLayout 類的一個實例對象,該類提供有 3 個構(gòu)造函數(shù),分別是:
QStackedLayout()
QStackedLayout(QWidget *parent)
QStackedLayout(QLayout *parentLayout)
借助第二個構(gòu)造函數(shù),我們可以將 QStackedLayout 添加到指定的 parent 窗口中;借助第三個構(gòu)造函數(shù),我們可以將 QStackedLayout 嵌入到指定的 parentLayout 布局控件中
本節(jié)學(xué)習(xí)的 5 種布局控件都可以嵌套使用,例如將 QVBoxLayout 放到 QHBoxLayout 內(nèi)部、將 QGridLayout 放到 QStackedLayout 內(nèi)部等。
下表羅列了操作 QStackedLayout 對象常用的一些成員方法:
成員方法 | 功 能 |
int QStackedLayout::addWidget(QWidget *widget) | 將 widget 控件添加到 QStackedLayout 控件中。 |
int QStackedLayout::insertWidget(int index, QWidget *widget) | 將 widget 控件插入到 QStackedLayout 控件指定的位置處。 |
信號函數(shù) | 功 能 |
void QStackedLayout::currentChanged(int index) | 切換當(dāng)前顯示的控件時,會觸發(fā)此信號,index 為顯示的新控件的索引。 |
void QStackedLayout::widgetRemoved(int index) | 移除某個控件時,會觸發(fā)此信號,index 為被移除控件的索引。 |
槽函數(shù) | 功 能 |
void setCurrentIndex(int index) | 將第 index 個控件作為要顯示的控件。 |
void QStackedLayout::setCurrentWidget(QWidget *widget) | 設(shè)置 widget 作為當(dāng)前要實現(xiàn)的控件。注意,必須保證 widget 存儲在 QStackedLayout 控件中。 |
這里我們以圖 10 所示的窗口為例,實現(xiàn)代碼如下:
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QStackedLayout>
#include <QListWidget>
#include <QHBoxLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建主窗口
QWidget widget;
widget.setWindowTitle("QStackedLayout分組布局");
widget.resize(600,400);
//向主窗口中添加一個水平布局控件
QHBoxLayout *layout=new QHBoxLayout;
//創(chuàng)建一個列表
QListWidget listWidget(&widget);
listWidget.setMinimumWidth(150);
listWidget.setFont(QFont("宋體",14));
listWidget.addItem("QPushButton");
listWidget.addItem("QLabel");
listWidget.addItem("QLineEdit");
//新建 3 個窗口,分別放置文本框、按鈕和單行輸入框
QWidget widget1;
widget1.setMinimumSize(400,400);
QPushButton but1("這是一個按鈕",&widget1);
QWidget widget2;
widget2.setMinimumSize(400,400);
QLabel lab1("這是一個文本框",&widget2);
QWidget widget3;
widget3.setMinimumSize(400,400);
QLineEdit edit("這是一個單行輸入框",&widget3);
//創(chuàng)建一個分組布局,將 3 個窗口添加到分組控件中
QStackedLayout *stackedLayout=new QStackedLayout;
stackedLayout->addWidget(&widget1);
stackedLayout->addWidget(&widget2);
stackedLayout->addWidget(&widget3);
//layout 第一列添加 QListWidget 控件,第二列添加分組布局控件,設(shè)置它們的伸縮系數(shù)比為 1:4
layout->addWidget(&listWidget,1);
layout->addLayout(stackedLayout,4);
//將 layout 水平布局控件添加到 widget 窗口中
widget.setLayout(layout);
widget.show();
//連接信號和槽,實現(xiàn)當(dāng)點(diǎn)擊列表中的某一項,切換分組布局管理器顯示的控件
QObject::connect(&listWidget,&QListWidget::currentRowChanged,stackedLayout,&QStackedLayout::setCurrentIndex);
return a.exec();
}
此程序中,我們在 QHBoxLayout 水平布局控件內(nèi)又放置了一個 QStackedLayout 分組布局控件。感興趣的讀者可以編寫程序,測試其它布局控件之間嵌套的效果。
默認(rèn)情況下,每個 Qt 項目都包含一個后綴名為.pro、名稱和項目名相同的文件,我們通常稱它為項目管理文件或者工程管理文件(簡稱 pro 文件)。
例如,新建一個 Qt 項目,如下圖所示:
圖 1 項目結(jié)構(gòu)
該項目的項目名為 Demo,項目中共包含 4 個文件,其中 Demo.pro 就是項目管理文件。
任何一個 Qt 項目都至少包含一個 pro 文件,此文件負(fù)責(zé)存儲與當(dāng)前項目有關(guān)的配置信息,比如:
項目中用到了哪些模塊?
項目中包含哪些源文件,哪些頭文件,它們的存儲路徑是什么?
項目使用哪個圖片作為應(yīng)用程序的圖標(biāo)?
項目最終生成的可執(zhí)行文件的名稱是什么?
所謂模塊,可以簡單地理解為文件夾或者壓縮包,內(nèi)部包含多個功能相近的類。作為一款成熟的 GUI 框架,Qt 提供了大量的類,根據(jù)這些類的功能,Qt 將它們分成了幾個組,每個組稱為一個模塊。打開 Qt Creator 的幫助界面并搜索“All modules”,可以看到 Qt 的所有模塊。
一個項目中可能包含上百個源文件,Qt 編譯這些源文件的方法是:先由 qmake 工具根據(jù) pro 文件記錄的配置信息生成相應(yīng)的 makefile 文件,然后執(zhí)行 make 命令完成對整個項目的編譯。也就是說,pro 文件存儲的配置信息是用來告知編譯器如何編譯當(dāng)前項目的,所以一個 Qt 項目要想完美運(yùn)行,既要保證各個源文件中程序的正確性,還要保證 pro 文件中配置信息的合理性。
對于一個剛剛創(chuàng)建好的 Qt 項目,pro 文件并不是空的,而是包含一些基本的配置信息。實際開發(fā)中,Qt 會自動修改 pro 文件的內(nèi)容,但有時也需要我們手動修改,例如程序中用到某個第三方庫時,就需要我們手動修改 pro 文件。
接下來,我們就為大家詳細(xì)地講解 pro 文件中各個配置信息的含義,以及如何手動修改 pro 文件。
在圖 1 所示的 Demo 項目中,雙擊打開 Demo.pro 項目管理文件,會看到如下內(nèi)容:
#-------------------------------------------------
#
# Project created by QtCreator 2021-08-31T16:05:04
#
#-------------------------------------------------
QT +=core gui
greaterThan(QT_MAJOR_VERSION, 4): QT +=widgets
TARGET=Demo
TEMPLATE=app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES +=QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES +=QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES +=main.cpp
mainwindow.cpp
HEADERS +=mainwindow.h
以上是 Demo.pro 配置文件中默認(rèn)包含的內(nèi)容。其中,#號是注釋符號,除了以#號開頭的注釋內(nèi)容外,其它內(nèi)容都是當(dāng)前項目的配置信息,比如QT +=core gui、TARGET=Demo等。
pro 文件可以存儲上百條配置信息,每條配置信息由三部分構(gòu)成:
前半部分是關(guān)鍵字,也稱配置項,用來指明配置信息的含義;
中間用 +=、-=、=等數(shù)學(xué)符號連接配置項和它對應(yīng)的值;
后半部分是配置項對應(yīng)的值,一個配置項可以對應(yīng)多個值,每個值代表不同的含義。
例如在 QT +=core gui中,Qt是配置項,core和gui是該配置項的值,中間用+=符號連接。下表給大家羅列了一些常用的配置項以及它們各自的含義。
配置項 | 含 義 |
QT | 指定項目中用到的所有模塊,默認(rèn)值為 core 和 gui,中間用 +=符號連接。 |
greaterThan(QT_MAJOR_VERSION, 4): QT +=widgets | 如果 QT 版本大于 4(Qt5 或更高版本),則需要添加 widgets 模塊,該模塊包含所有控件類。 |
TARGET | 指定程序成功運(yùn)行后生成的可執(zhí)行文件的名稱,中間用=符號連接。 |
TEMPLATE | 指定如何運(yùn)行當(dāng)前程序,默認(rèn)值為 app,表示當(dāng)前程序是一個應(yīng)用程序,可以直接編譯、運(yùn)行。常用的值還有 lib,表示將當(dāng)前程序編譯成庫文件。 |
DEFINES | 在程序中新定義一個指定的宏,比如 DEFINES +=xxx,如同在程序中添加了 #define xxx 語句。 |
SOURCES | 指定項目中包含的所有 .cpp 源文件。 |
HEADERS | 指定項目中包含的所有 .h 頭文件。 |
FORMS | 指定項目中包含的 ui 文件。 |
INCLUDEPATH | 指定頭文件的存儲路徑,例如:INCLUDEPATH +=/opt/ros/include |
CONFIG | 經(jīng)常對應(yīng)的值有:release:以 release 模式編譯程序;debug:以 debug 模式編譯程序;warn_on:編譯器輸出盡可能多的警告;c++11:啟動 C++11 標(biāo)準(zhǔn)支持。例如 CONFIG +=c++11。 |
根據(jù)上表中對各個配置項的講解,您可以很輕松地搞清楚 Demo.pro 文件中各個配置項的含義,這里不再過多贅述。
上表中,大部分配置項不需要我們手動修改,比如 SOURCES、HEADERS、FORMS 等,當(dāng)我們添加或者刪除項目中的源文件時,Qt 會自動修改這些配置項。有些配置項需要手動修改,比如 QT 配置項,接下來重點(diǎn)給大家講解 QT 的用法。
前面提到,Qt 根據(jù)各個類的功能將它們分到不同的模塊,因此程序中要想使用某個類,必須完成兩項準(zhǔn)備工作:
引入包含該類的頭文件,通常情況下,Qt 中每個類的類名和包含它的頭文件的名稱是相同的,比如 QWiget 窗口類位于<QWidget>頭文件中;
將該類所屬的模塊添加到 pro 項目管理文件中。
QT用來指明當(dāng)前項目中用到的所有模塊,它的默認(rèn)值是 core 和 gui,分別表示引入 Core 模塊和 GUI 模塊:
Core 模塊包含了 Qt GUI 界面開發(fā)的核心功能,其它所有模塊都需要依賴于這個模塊,它是所有 Qt GUI 項目必備的模塊;
GUI 模塊提供了用于開發(fā) GUI 應(yīng)用程序的必要的一些類。
每個新創(chuàng)建的 Qt GUI 項目中,都默認(rèn)包含 Core 模塊和 GUI 模塊,如果項目中用不到它們,可以使用QT -=刪除。例如,刪除項目中包含的 GUI 模塊,只需在 pro 文件中添加一條配置信息:
QT -=gui
除了 Core 和 GUI 模塊外,Qt 還有 SQL(包含操作數(shù)據(jù)庫相關(guān)的類)、Widgets(包含構(gòu)建界面的所有控件類)、Multimedia(包含提供音頻、視頻等功能的類)等模塊,Qt 項目不會自動包含這些模塊。例如,項目中用到 SQL 模塊中的一些類時,需要在 pro 文件中添加如下配置信息:
QT +=sql
那么,當(dāng)程序中用到某個類時,如何知道它屬于哪個模塊呢?很簡單,先將該類所在的頭文件中引入到程序中,然后鼠標(biāo)選中頭文件并按Fn+F1組合鍵,打開該頭文件的使用手冊后就可以看到它所屬的模塊。以程序中使用 QWidget 窗口類為例,先在程序中添加如下語句:
#include <QWidget>
緊接著,鼠標(biāo)選中“QWidget”并按Fn+F1組合鍵,打開下圖所示的 QWdiget 類使用手冊,可以看到該類所屬的模塊為 widgets。
圖 2 查看類所屬的模塊
實際開發(fā)中,如果僅使用 Qt 提供的信號函數(shù)和槽函數(shù),會經(jīng)常遇到信號函數(shù)的參數(shù)類型和個數(shù)無法滿足實際需求、信號函數(shù)和槽函數(shù)的參數(shù)類型不匹配等問題。解決此類問題,最簡單有效的方式就是:自定義場景需要的信號函數(shù)和槽函數(shù)。
自定義信號函數(shù)
信號函數(shù)指的是符合以下條件的函數(shù):
定義在某個類中,該類直接或間接繼承自 QObject 類;
用 signals 關(guān)鍵字修飾;
函數(shù)只需要聲明,不需要定義(實現(xiàn));
函數(shù)的返回值類型為 void,參數(shù)的類型和個數(shù)不限。
舉個簡單的例子:
class MyWidget:public QWidget{
//Q_OBJECT 是一個宏,添加它才能正常使用 Qt 的信號和槽機(jī)制
Q_OBJECT
//修飾信號函數(shù)的關(guān)鍵字
signals:
//自定義的信號函數(shù)
void MySignal(QString message);
};
我們自定義了一個繼承自 QWidget 的 MyWidget 類,QWidget 是 QObject 的子類,所以 MyWidget 間接繼承自 QObject 類。MyWidget 類中自定義了名為 MySignal 的信號函數(shù)(可以簡稱 MySignal 信號),它用 signals 關(guān)鍵字修飾,沒有返回值,也沒有定義(實現(xiàn)),僅有 1 個參數(shù)。
對于 MySignal() 信號函數(shù),程序中不會直接調(diào)用它,而是借助 connect() 連接某個槽函數(shù),實現(xiàn)的語法格式是:
MyWidget myWidget;
QObject::connect(&myWidget,&MyWidget::MySignal,信號接收者,槽函數(shù));
一旦確定了信號接收者和槽函數(shù),當(dāng) MySignal 信號發(fā)出后,與之相連的槽函數(shù)就會執(zhí)行。那么,程序中如何發(fā)出 MySignal 信號呢?
對于 Qt 提供給我們的信號函數(shù),其底層已經(jīng)設(shè)置好了信號發(fā)出的時機(jī),例如按下鼠標(biāo)時、點(diǎn)擊 Enter 回車鍵時等等。對于自定義的信號,我們需要自己指定信號發(fā)出的時機(jī),這就需要用到 emit 關(guān)鍵字。emit 中文意思為“發(fā)出、射出”,是 Qt 在 C++ 基礎(chǔ)上擴(kuò)展的一個關(guān)鍵字,專門用來發(fā)射信號。
以定義好的 MySignal 信號為例,修改 MyWidget 類為:
class MyWidget:public QWidget{
//Q_OBJECT 是一個宏,添加它才能正常使用 Qt 的信號和槽機(jī)制
Q_OBJECT
//自定義信號函數(shù)
signals:
void MySignal(QString mess);
public:
void emitSignal(){
emit MySignal(message);
}
private:
QString message;
};
我們?yōu)?MyWidget 類新增了一個 emitSignal() 方法和一個 message 屬性,emitSignal() 方法中的emit MySignal(message);語句就表示發(fā)射 MySignal 信號。當(dāng)程序中執(zhí)行 emitSingal() 函數(shù)時,就會發(fā)出 MySignal 信號,message 屬性的值也會隨信號一同發(fā)出,對應(yīng)的槽函數(shù)可以接收到 message 的值。
對于每一個自定義的信號函數(shù),程序中都應(yīng)該提供發(fā)射該信號的方法(函數(shù)),而且這樣的方法(函數(shù))可以有多個。
Qt5 中,槽函數(shù)既可以是普通的全局函數(shù)、也可以是類的成員函數(shù)、靜態(tài)成員函數(shù)、友元函數(shù)、虛函數(shù),還可以用 lambda 表達(dá)式表示。
和信號函數(shù)不同,槽函數(shù)必須手動定義(實現(xiàn))。槽函數(shù)可以在程序中直接調(diào)用,但主要用來響應(yīng)某個信號。自定義一個槽函數(shù)時,需要注意以下幾點(diǎn):
槽函數(shù)的返回值必須和信號函數(shù)相同,由于信號函數(shù)的返回值一定是 void,所以槽函數(shù)的返回值也必須為 void;
對于帶參的信號函數(shù),槽函數(shù)可以選擇接收所有參數(shù),則參數(shù)的類型、順序、個數(shù)都必須與信號函數(shù)相同;也可以選擇接收前幾個參數(shù),這些參數(shù)的類型、順序都必須與信號函數(shù)相同;還可以選擇不接受任何參數(shù)。
槽函數(shù)的參數(shù)個數(shù)只能比信號函數(shù)少,不能比信號函數(shù)多;
槽函數(shù)的參數(shù)不能有默認(rèn)值。
舉個例子,自定義響應(yīng) MySignal 信號的槽函數(shù):
class MyWidget:public QWidget{
//Q_OBJECT 是一個宏,添加它才能正常使用 Qt 的信號和槽機(jī)制
Q_OBJECT
signals:
void MySignal(QString mess1,QString mess2);
public:
void emitSignal(){
emit MySignal(message1,message2);
}
//類的成員函數(shù)
void recSlot1(QString mess){
qDebug() << "執(zhí)行 recSlot1() 成員函數(shù),輸出" << mess;
}
//指明定義的是槽函數(shù)
public slots:
void recSlot2(QString mess1,QString mess2){
qDebug() << "執(zhí)行 recSlot2() 槽函數(shù),輸出"<< mess1 << " " << mess2;
}
public:
QString message1;
QString message2;
};
//全局函數(shù)
void recSlot3(){
qDebug() << "執(zhí)行 recSlot3() 全局函數(shù)";
}
程序中,重點(diǎn)關(guān)注 recSlot1()、recSlot2()、recSlot3() 這 3 個函數(shù):
recSlot1() 是 MyWidget 類內(nèi)部的 public 成員函數(shù),可以當(dāng)做槽函數(shù)使用;
recSlot2() 位于 MyWidget 類的內(nèi)部,修飾它的關(guān)鍵字是 public slots。slots 和 emit 一樣,是 Qt 擴(kuò)展的一個關(guān)鍵字,專門用來修飾槽函數(shù)。也就是說,recSlot2() 是 MyWidget 類中的槽函數(shù)。
recSlot3() 是全局函數(shù),可以當(dāng)做槽函數(shù)使用。
slots 關(guān)鍵字可以和 public、protected、private 搭配使用,它們的區(qū)別是:
public slots:該關(guān)鍵字修飾的槽函數(shù),既可以在當(dāng)前類及其子類的成員函數(shù)中調(diào)用,也可以在類外部的其它函數(shù)(比如 main() 函數(shù))中調(diào)用;
protected slots:該關(guān)鍵字修飾的槽函數(shù),僅允許在當(dāng)前類及其子類的成員函數(shù)內(nèi)調(diào)用,不能在類外部的其它函數(shù)內(nèi)調(diào)用;
private slots:該關(guān)鍵字修飾的槽函數(shù),只允許在當(dāng)前類的成員函數(shù)內(nèi)調(diào)用,不能在子類中調(diào)用,也不能在類外部的其它函數(shù)內(nèi)調(diào)用。
通常情況下,槽函數(shù)使用 public slots 修飾。
很多讀者會問,既然 public 修飾的成員函數(shù)可以當(dāng)做槽函數(shù),為什么還要提供 slots 關(guān)鍵字呢?筆者認(rèn)為,“兼容舊的 Qt 版本”是其中的一個原因。Qt4 中的槽函數(shù)只能是 slots 修飾的類成員函數(shù),Qt5 中取消了這一限制,但考慮到要兼容舊的 Qt 版本,Qt5 保留了舊版本中 connect() 函數(shù)的語法格式,也保留了 slots 關(guān)鍵字。
調(diào)用 connect() 函數(shù),將 MySignal() 信號函數(shù)分別連接 recSlot1()、recSlot2()、recSlot3() 三個槽函數(shù),實現(xiàn)代碼為://類的成員函數(shù)作為槽函數(shù)QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot1);//信號函數(shù)和槽函數(shù)相連接QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot2);//全局函數(shù)作為槽函數(shù)QObject::connect(&mywidget,&MyWidget::MySignal,&recSlot3);
//main.cpp
#include <QApplication>
#include <QWidget>
#include <QDebug>
class MyWidget:public QWidget{
//Q_OBJECT 是一個宏,添加它才能正常使用 Qt 的信號和槽機(jī)制
Q_OBJECT
//信號函數(shù)
signals:
void MySignal(QString mess1,QString mess2);
public:
//發(fā)射信號的函數(shù)
void emitSignal(){
emit MySignal(message1,message2);
}
//普通類成員函數(shù)
void recSlot1(QString mess){
qDebug() << "執(zhí)行 recSlot1() 成員函數(shù),輸出" << mess;
}
//槽函數(shù)
public slots:
void recSlot2(QString mess1,QString mess2){
qDebug() << "執(zhí)行 recSlot2() 槽函數(shù),輸出"<< mess1 << " " << mess2;
}
public:
QString message1;
QString message2;
};
//全局函數(shù)
void recSlot3(){
qDebug() << "執(zhí)行 recSlot3() 全局函數(shù)";
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//創(chuàng)建主窗口
MyWidget mywidget;
mywidget.message1="C老舅";
mywidget.message2="http://www.baidu.net";
//類的成員函數(shù)作為槽函數(shù)
QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot1);
//信號函數(shù)和槽函數(shù)相連接
QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot2);
//全局函數(shù)作為槽函數(shù)
QObject::connect(&mywidget,&MyWidget::MySignal,&recSlot3);
mywidget.show();
//發(fā)射 Signal 信號
mywidget.emitSignal();
return a.exec();
}
//MyWidget類的定義應(yīng)該放到 .h 文件中,本例中將其寫到 main.cpp 中,程序最后需要添加 #include "當(dāng)前源文件名.moc" 語句,否則無法通過編譯。
#include "main.moc"
執(zhí)行程序,會彈出一個 myWidget 空白窗口,同時輸出以下信息:
執(zhí)行 recSlot1() 成員函數(shù),輸出 “老舅”
執(zhí)行 recSlot2() 槽函數(shù),輸出 “ 老舅” “http://www.baidu.net”
執(zhí)行 recSlot3() 全局函數(shù)
很多應(yīng)用程序都需要具備操作文件的能力,包括對文件內(nèi)容進(jìn)行讀/寫、創(chuàng)建和刪除文件等,甚至某些應(yīng)用程序的誕生純粹是為了操作文件,比如 WPS Office、PDFedit 等。為此,Qt 框架提供了 QFile 類專門用來操作文件。
QFile文件操作
QFile 類支持對文件進(jìn)行讀取、寫入、刪除、重命名、拷貝等操作,它既可以操作文件文件,也可以操作二進(jìn)制文件。
使用 QFile 類操作文件之前,程序中需引入<QFile>頭文件。創(chuàng)建 QFile 類的對象,常用的構(gòu)造函數(shù)有:
QFile::QFile()
QFile::QFile(const QString &name)
參數(shù) name 用來指定要操作的目標(biāo)文件,包含文件的存儲路徑和文件名,存儲路徑可以使用絕對路徑(比如 “D:/Demo/test.txt”)或者相對路徑(比如"./Demo/test.txt"),路徑中的分隔符要用 “/” 表示。
通常情況下,我們會調(diào)用第二個構(gòu)造函數(shù),直接指明要操作的文件。對于第一個構(gòu)造函數(shù)創(chuàng)建的 QFile 對象,需要再調(diào)用 setFileName() 方法指明要操作的文件。
與 C++ 讀寫文件的規(guī)則一樣,使用 QFile 讀寫文件之前必須先打開文件,調(diào)用 open() 成員方法即可,常用的語法格式為:
bool QFile::open(OpenMode mode)
mode 參數(shù)用來指定文件的打開方式,下表羅列了此參數(shù)的可選值以及各自的含義:
打開方式 | 含 義 |
QIODevice::ReadOnly | 只能對文件進(jìn)行讀操作 |
QIODevice::WriteOnly | 只能對文件進(jìn)行寫操作,如果目標(biāo)文件不存在,會自行創(chuàng)建一個新文件。 |
QIODevice::ReadWrite | 等價于 ReadOnly | WriteOnly,能對文件進(jìn)行讀和寫操作。 |
QIODevice::Append | 以追加模式打開文件,寫入的數(shù)據(jù)會追加到文件的末尾(文件原有的內(nèi)容保留)。 |
QIODevice::Truncate | 以重寫模式打開,寫入的數(shù)據(jù)會將原有數(shù)據(jù)全部清除。注意,此打開方式不能單獨(dú)使用,通常會和 ReadOnly 或 WriteOnly 搭配。 |
QIODevice::Text | 讀取文件時,會將行尾結(jié)束符(Unix 系統(tǒng)中是 “\n”,Windows 系統(tǒng)中是 “\r\n”)轉(zhuǎn)換成‘\n’;將數(shù)據(jù)寫入文件時,會將行尾結(jié)束符轉(zhuǎn)換成本地格式,例如 Win32 平臺上是‘\r\n’。 |
根據(jù)需要,可以為 mode 參數(shù)一次性指定多個值,值和值之間用|分割。比如:
QIODevice::ReadOnly | QIODevice::Text:表示只允許對文件進(jìn)行讀操作,讀取文件時,會將行尾結(jié)束符轉(zhuǎn)換為 ‘\n’;
QIODevice::WriteOnly | QIODevice::Text:表示只允許對文件進(jìn)行寫操作,將數(shù)據(jù)寫入文件時,會將行尾結(jié)束符轉(zhuǎn)換為本地格式;
QIODevice::ReadWrite | QIODevice::Append | QIODevice::Text:表示對文件進(jìn)行寫操作,寫入的數(shù)據(jù)會存放到文件的尾部,同時數(shù)據(jù)中的行尾結(jié)束符轉(zhuǎn)換為本地格式。
注意,傳遞給 mode 參數(shù)的多個值之間不能相互沖突,比如 Append 和 Truncate 不能同時使用。
如果文件成功打開,open() 函數(shù)返回 true,否則返回 false。
QFile 類提供了很多功能實用的方法,可以快速完成對文件的操作,下表列舉了常用的一些:
普通成員方法 | 功 能 |
qint64 QFile::size() const | 獲取當(dāng)前文件的大小。對于打開的文件,該方法返回文件中可以讀取的字節(jié)數(shù)。 |
bool QIODevice::getChar(char *c) | 從文件中讀取一個字符,并存儲到 c 中。讀取成功時,方法返回 true,否則返回 false。 |
bool QIODevice::putChar(char c) | 向文件中寫入字符 c,成功時返回 true,否則返回 false。 |
QByteArray QIODevice::read(qint64 maxSize) | 從文件中一次性最多讀取 maxSize 個字節(jié),然后返回讀取到的字節(jié)。 |
qint64 QIODevice::read(char *data, qint64 maxSize) | 從文件中一次性對多讀取 maxSize 個字節(jié),讀取到的字節(jié)存儲到 data 指針指定的內(nèi)存控件中。該方法返回成功讀取到的字節(jié)數(shù)。 |
QByteArray QIODevice::readAll() | 讀取文件中所有的數(shù)據(jù)。 |
qint64 QIODevice::readLine(char *data, qint64 maxSize) | 每次從文件中讀取一行數(shù)據(jù)或者讀取最多 maxSize-1 個字節(jié),存儲到 data 中。該方法返回實際讀取到的字節(jié)數(shù)。 |
qint64 QIODevice::write(const char *data, qint64 maxSize) | 向 data 數(shù)據(jù)一次性最多寫入 maxSize 個字節(jié),該方法返回實際寫入的字節(jié)數(shù)。 |
qint64 QIODevice::write(const char *data) | 將 data 數(shù)據(jù)寫入文件,該方法返回實際寫入的字節(jié)數(shù)。 |
qint64 QIODevice::write(const QByteArray &byteArray) | 將 byteArray 數(shù)組中存儲的字節(jié)寫入文件,返回實際寫入的字節(jié)數(shù)。 |
bool QFile::copy(const QString &newName) | 將當(dāng)前文件的內(nèi)容拷貝到名為 newName 的文件中,如果成功,方法返回 true,否則返回 false。 copy 方法在執(zhí)行復(fù)制操作之前,會關(guān)閉源文件。 |
bool QFile::rename(const QString &newName) | 對當(dāng)前文件進(jìn)行重命名,新名稱為 newName,成功返回 true,失敗返回 false。 |
bool QFile::remove() | 刪除當(dāng)前文件,成功返回 true,失敗返回 false。 |
【實例一】演示了 QFile 類讀寫文本文件的過程。
#include <QFile>#include <QDebug>int main(int argc, char *argv[]){ //創(chuàng)建 QFile 對象,同時指定要操作的文件 QFile file("D:/demo.txt"); //對文件進(jìn)行寫操作 if(!file.open(QIODevice::WriteOnly|QIODevice::Text)){ qDebug()<<"文件打開失敗"; } //向文件中寫入兩行字符串 file.write("老舅\n"); file.write("http://www.baidu.net"); //關(guān)閉文件 file.close(); //重新打開文件,對文件進(jìn)行讀操作 if(!file.open(QIODevice::ReadOnly|QIODevice::Text)){ qDebug()<<"文件打開失敗"; } //每次都去文件中的一行,然后輸出讀取到的字符串 char * str=new char[100]; qint64 readNum=file.readLine(str,100); //當(dāng)讀取出現(xiàn)錯誤(返回 -1)或者讀取到的字符數(shù)為 0 時,結(jié)束讀取 while((readNum !=0) && (readNum !=-1)){ qDebug() << str; readNum=file.readLine(str,100); } file.close(); return 0;}#include <QFile>
#include <QDebug>
int main(int argc, char *argv[])
{
//創(chuàng)建 QFile 對象,同時指定要操作的文件
QFile file("D:/demo.txt");
//對文件進(jìn)行寫操作
if(!file.open(QIODevice::WriteOnly|QIODevice::Text)){
qDebug()<<"文件打開失敗";
}
//向文件中寫入兩行字符串
file.write("老舅\n");
file.write("http://www.baidu.net");
//關(guān)閉文件
file.close();
//重新打開文件,對文件進(jìn)行讀操作
if(!file.open(QIODevice::ReadOnly|QIODevice::Text)){
qDebug()<<"文件打開失敗";
}
//每次都去文件中的一行,然后輸出讀取到的字符串
char * str=new char[100];
qint64 readNum=file.readLine(str,100);
//當(dāng)讀取出現(xiàn)錯誤(返回 -1)或者讀取到的字符數(shù)為 0 時,結(jié)束讀取
while((readNum !=0) && (readNum !=-1)){
qDebug() << str;
readNum=file.readLine(str,100);
}
file.close();
return 0;
}
執(zhí)行程序,“老舅” 和 “http://www.baidu.net” 先寫入 D 盤的 demo.txt 文件,然后再從文件中將它們讀取出來。
【實例二】演示 QFile 讀寫二進(jìn)制文件的過程。
#include <QFile>
#include <QDebug>
int main(int argc, char *argv[])
{
//指定要寫入文件的數(shù)據(jù)
qint32 nums[5]={1,2,3,4,5};
//寫入文件之前,要將數(shù)據(jù)以二進(jìn)制方式存儲到字節(jié)數(shù)組中
QByteArray byteArr;
byteArr.resize(sizeof(nums));
for(int i=0;i<5;i++){
//借助指針,將每個整數(shù)拷貝到字節(jié)數(shù)組中
memcpy(byteArr.data()+i*sizeof(qint32),&(nums[i]),sizeof(qint32));
}
//將 byteArr 字節(jié)數(shù)組存儲到文件中
QFile file("D:/demo.dat");
file.open(QIODevice::WriteOnly);
file.write(byteArr);
file.close();
//再次打開文件,讀取文件中存儲的二進(jìn)制數(shù)據(jù)
file.open(QIODevice::ReadOnly);
QByteArray resArr=file.readAll();
//輸出讀取到的二進(jìn)制數(shù)據(jù)
qDebug()<<"resArr: "<<resArr;
//將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為整數(shù)
char* data=resArr.data();
while(*data){
qDebug() << *(qint32*)data;
data +=sizeof(qint32);
}
return 0;
}
執(zhí)行程序,demo.dat 文件中會存儲 {1,2,3,4,5} 這 5 個整數(shù)的二進(jìn)制形式,同時輸出以下內(nèi)容:
resArr: “\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00”
1
2
3
4
5
單獨(dú)使用 QFile 類讀寫文件的過程既繁瑣又復(fù)雜,Qt 提供了兩個輔助類 QTextStream 和 QDataStream,前者用來讀寫文件文件,后者用來讀寫二進(jìn)制文件,QFile 可以和它們搭配使用,從整體上提高讀寫文件的開發(fā)效率。
和單獨(dú)使用 QFile 類讀寫文本文件相比,QTextStream 類提供了很多讀寫文件相關(guān)的方法,還可以設(shè)定寫入到文件中的數(shù)據(jù)格式,比如對齊方式、寫入數(shù)字是否帶前綴等等。
使用 QTextStream 類之前,程序中要先引入<QTextStream>頭文件。QTextStream 類提供了很多種構(gòu)造函數(shù),常用的是:
QTextStream(QIODevice *device)
QIODevice 是 QFile 的父類,因此在構(gòu)造 QTextStream 類的對象時,需要傳遞一個 QFile 類的對象。
下表羅列了 QTextStream 類常用的一些方法:
成員方法 | 功 能 |
bool QTextStream::atEnd() const | 判斷是否讀到文件末尾,如果已經(jīng)達(dá)到末尾,返回 true,否則返回 false。 |
QString QTextStream::read(qint64 maxlen) | 從文件中讀最多 maxlen 個字符,返回這些字符組成的 QString 字符串。 |
QString QTextStream::readAll() | 從文件中讀取所有內(nèi)容,返回由讀取內(nèi)容組成的 QString 字符串。 |
QString QTextStream::readLine(qint64 maxlen=0) | 默認(rèn)讀取一行文本,如果手動指定 maxlen 的值,則最多讀取 maxlen 個字符,并返回讀取內(nèi)容組成的 QString 字符串。 |
void QTextStream::setFieldAlignment(FieldAlignment mode) | 設(shè)置對齊方式,通常與 setFieldWidth() 一起使用。 |
void QTextStream::setFieldWidth(int width) | 設(shè)置每份數(shù)據(jù)占用的位置寬度為 width。 |
QTextStream 類重載了>>輸入運(yùn)算符和>>輸出運(yùn)算符,使讀寫文本文件變得更簡單。例如,用 QTextStream 實現(xiàn)【實例一】的程序如下:
#include <QFile>
#include <QDebug>
#include <QString>
#include <QTextStream>
int main(int argc, char *argv[])
{
//創(chuàng)建 QFile 對象,同時指定要操作的文件
QFile file("D:/demo.txt");
//對文件進(jìn)行寫操作
if(!file.open(QIODevice::WriteOnly|QIODevice::Text)){
qDebug()<<"文件打開失敗";
}
QTextStream out(&file);
//向文件中寫入兩行字符串
out << (QString)"老舅\n" << (QString)"http://www.baidu.net";
//關(guān)閉文件
file.close();
//重新打開文件,對文件進(jìn)行讀操作
if(!file.open(QIODevice::ReadOnly|QIODevice::Text)){
qDebug()<<"文件打開失敗";
}
QTextStream in(&file);
//一直讀,直至讀取失敗
while(!in.atEnd()){
QString str;
//從文件中讀取一個字符串
in >> str;
qDebug() << str;
}
file.close();
return 0;
}
和<iostream>類似,QTextStream 類提供了兩種格式化輸出的方法,一種是調(diào)用該類的成員方法,例如表 3 中的 setFieldAlignment()、setFieldWidth 等,另一種是調(diào)用 QTextStream 類提供的格式描述符,下表羅列了常用的一些:
描述符 | 功能相同的方法 |
Qt::hex | QTextStream::setIntegerBase(16) |
Qt::showbase | QTextStream::setNumberFlags(numberFlags() | ShowBase) |
Qt::forcesign | QTextStream::setNumberFlags(numberFlags() | ForceSign) |
Qt::fixed | QTextStream::setRealNumberNotation(FixedNotation) |
Qt::scientific | QTextStream::setRealNumberNotation(ScientificNotation) |
Qt::left | QTextStream::setFieldAlignment(AlignLeft) |
Qt::right | QTextStream::setFieldAlignment(AlignRight) |
Qt::center | QTextStream::setFieldAlignment(AlignCenter) |
舉個簡單的例子:
#include <QFile>
#include <QDebug>
#include <QString>
#include <QTextStream>
int main(int argc, char *argv[])
{
QFile file("D:/demo.txt");
if(!file.open(QIODevice::WriteOnly|QIODevice::Text)){
qDebug()<<"文件打開失敗";
}
QTextStream out(&file);
//將 10 的十六進(jìn)制數(shù)寫入文件
out << hex << 10;
//設(shè)置每份數(shù)據(jù)占用 10 個字符的位置
out.setFieldWidth(10);
//以右對齊的方式寫入 3.14
out << left << 3.14;
//后續(xù)數(shù)據(jù)以左對齊的方式寫入文件
out.setFieldAlignment(QTextStream::AlignRight);
out << 2.7;
//關(guān)閉文件
file.close();
return 0;
}
程序運(yùn)行后,demo.txt 存儲的文本內(nèi)容為:
a3.14 2.7
QFile+QDataStream
QDataStream 類的用法和 QTextStream 非常類似,最主要的區(qū)別在于,QDataStream 用于讀寫二進(jìn)制文件。
使用 QDataStream 類之前,程序中要引入<QDataStream>頭文件。創(chuàng)建 QDataStream 對象常用的構(gòu)造函數(shù)為:
QDataStream::QDataStream(QIODevice *d)
下表羅列了 QDataStream 類常用的成員方法:
成員方法 | 功 能 |
bool QDataStream::atEnd() const | 判斷是否讀到文件末尾,如果已經(jīng)達(dá)到末尾,返回 true,否則返回 false。 |
QDataStream &QDataStream::readBytes(char *&s, uint &l) | 對于用 writeBytes() 方法寫入文件的 l 和 s,只能使用 readBytes() 方法讀取出來。 |
int QDataStream::readRawData(char *s, int len) | 從文件中讀取最多 len 字節(jié)的數(shù)據(jù)到 s 中,返回值表示實際讀取的字節(jié)數(shù)。注意,調(diào)用該方法之前,需要先給 s 參數(shù)分配好內(nèi)存空間。 |
void QDataStream::setVersion(int v) | 不同版本的 Qt 中,同名稱的數(shù)據(jù)類型也可能存在差異,通過調(diào)用此方法手動指定版本號,可以確保讀取數(shù)據(jù)的一致性。 |
int QDataStream::skipRawData(int len) | 跳過文件中的 len 個字節(jié),返回實際跳過的字節(jié)數(shù)。 |
QDataStream &QDataStream::writeBytes(const char *s, uint len) | 將長度 len 和 s 一起寫入到文件中,對于 writeBytes() 寫入的數(shù)據(jù),只能用 readBytes() 方法讀取。 |
int QDataStream::writeRawData(const char *s, int len) | 將 s 中前 len 字節(jié)的數(shù)據(jù)寫入文件,返回值表示成功寫入的字節(jié)數(shù)。 |
QDataStream 類也對<<和>>進(jìn)行了重載,舉個簡單的例子,用 QDataStream 重新實現(xiàn)實例二:
#include <QFile>
#include <QDebug>
#include <QDataStream>
int main(int argc, char *argv[])
{
//指定要寫入文件的數(shù)據(jù)
qint32 nums[5]={1,2,3,4,5};
QFile file("D:/demo.dat");
file.open(QIODevice::WriteOnly);
//創(chuàng)建 QDataStream 對象
QDataStream out(&file);
//將 nums 數(shù)組中的整數(shù)逐個寫入到二進(jìn)制文件中
for(int i=0;i<5;i++){
out << nums[i];
}
file.close();
//再次打開文件,讀取文件中存儲的二進(jìn)制數(shù)據(jù)
file.open(QIODevice::ReadOnly);
QDataStream in(&file);
//讀取二進(jìn)制文件中的數(shù)據(jù)
while(!in.atEnd()){
//每次讀取一個整數(shù)
qint32 num;
in >> num;
qDebug() << num;
}
return 0;
}
輸出結(jié)果為:
1
2
3
4
5
本節(jié)我們教大家用 Qt 實現(xiàn)一個帶界面的學(xué)生信息管理系統(tǒng),完成后的系統(tǒng)主界面如下圖所示:
圖 1 學(xué)生信息管理系統(tǒng)主界面
該學(xué)生信息管理系統(tǒng)將學(xué)生信息保存到文件中,用戶借助界面上的表格、列表、按鈕、輸入框等控件,可以對學(xué)生信息進(jìn)行查看、添加、刪除、查找、更改、保存等操作。
整個學(xué)生信息管理系統(tǒng),需要設(shè)計兩個界面,一個是圖 1 所示的主界面,另一個是添加學(xué)生信息的界面,如下圖所示:
圖 2 添加學(xué)生信息界面
主界面的設(shè)計實現(xiàn)思路是:將 QHBoxLayout 作為主界面的布局工具,內(nèi)部添加兩個 QGroupBox 分組框,從而將整個界面一分為二:
左邊的分組框中添加一個 QTableWidget 表格控件,如果想讓表格控件的尺寸隨著主窗口尺寸的變化而變化,可以將 QTableWidget 框架添加到某個布局工具中(比如 QHBoxLayout、QVBoxLayout 等)。
右邊的分組框中,向 QVBoxLayout 先后添加一個 QListWidget 和 QGridLayout,從而將右側(cè)分組框分為上下兩部分。在 QGridLayout 中放置添加、刪除、保存、退出按鈕以及一個 QLineEdit 單行輸入框。
由此,主窗口就設(shè)計完成了。
設(shè)計圖 2 所示的添加學(xué)生信息界面非常簡單,只需要自定義一個繼承自 QDialog 的窗口類,用 QVBoxLayout 作為該窗口的布局工具,并依次將 QFormLayout 和 QHBoxLayout 添加到 QVBoxLayout 中:
向 QFormLayout 中添加多個單行輸入框;
向 QHBoxLayout 中添加兩個按鈕。
由此,添加學(xué)生信息的窗口就設(shè)計完成了。
學(xué)生信息管理系統(tǒng)的功能實現(xiàn)
整個學(xué)生信息管理系統(tǒng),由以下幾個文件構(gòu)成:
圖 4 項目結(jié)構(gòu)
各個文件的作用分別是:
StuInfoFile.pro:項目文件,整個項目的實現(xiàn)并沒有手動修改此文件中的內(nèi)容;
main.cpp:整個項目的主程序文件,用來啟動主界面;
MainWidget.h 和 MainWidget.cpp:自定義的窗口類(繼承自 QWdiget),實現(xiàn)圖 1 所示的主界面,并完成主界面中所有控件的功能;
EditStuMessBox.h 和 EditStuMessBox.cpp:自定義的窗口類(繼承自 QDialog),實現(xiàn)圖 2 所示的界面,并完成添加學(xué)生信息界面的功能;
Tool.h 和 Tool.cpp:包含一些公共的宏定義和函數(shù);
image.qrc:為項目中的窗口添加 icon 圖標(biāo)。
整個項目的實現(xiàn)過程,需要重點(diǎn)說明的有以下幾點(diǎn):
1、合理使用信號和槽
對于關(guān)聯(lián)在一起的信號函數(shù)和槽函數(shù),有些場景中,需要調(diào)用 disconnect() 暫時取消它們之間的連接,后續(xù)再重新關(guān)聯(lián)它們。
例如在本項目中,MainWidget.cpp 文件 flushTable() 函數(shù)的功能是更新 QTableWidget 表格控件中顯示的學(xué)生信息。更新學(xué)生信息之前,需要調(diào)用 disconnect() 函數(shù)切斷 cellChanged() 信號函數(shù)與其它所有槽函數(shù)的關(guān)聯(lián),然后才能正常更新數(shù)據(jù),更新完成后再恢復(fù) cellChanged() 與其它槽函數(shù)的關(guān)聯(lián)。
之所以更新數(shù)據(jù)前必須切斷 cellChanged() 與其它槽函數(shù)的關(guān)聯(lián),是因為更新表格數(shù)據(jù)會不斷地觸發(fā) cellChanged() 信號,最終會導(dǎo)致程序崩潰。
2、從文件中刪除和更改某個學(xué)生信息
實現(xiàn)對學(xué)生信息的“增刪查改”操作中,刪除和修改學(xué)生信息的實現(xiàn)過程更復(fù)雜一些,本項目中采取的實現(xiàn)方法是:不斷地從 student.txt 文件中讀取學(xué)生信息,判斷讀取到的學(xué)生信息是否需要刪除或修改,如果不需要,則直接寫入 student_temp.txt 文件;反之如果需要刪除,則直接將讀取到的信息丟棄,如果需要修改,則將修改后的學(xué)生信息寫入到 student_temp.txt 文件中。
最終,student_temp.txt 文件中存儲的就是最新的學(xué)生信息,我們可以將 student.txt 文件中的內(nèi)容刪除,然后將 student_temp.txt 文件中的內(nèi)容拷貝到 student.txt 文件中,最后刪除 student_temp.txt 文件;也可以直接刪除 student.txt 文件,然后將 student_temp.txt 文件的名稱改為 student.txt,本項目中采用的是第二種方法。
3、為項目添加圖標(biāo)
圖 4 中,image.qrc 文件是用來為項目添加圖標(biāo)的,本項目選用的是 C 語言中文網(wǎng)的 icon 圖標(biāo),就存儲在當(dāng)前項目的文件夾內(nèi),如下圖所示:
圖 5 項目的所有文件
首先,我們要在項目中新建一個后綴名為 qrc 的文件,鼠標(biāo)移動到項目名上右擊選擇“添加新文件”,Qt Creator 會彈出如下對話框:
圖 6 創(chuàng)建 qrc 文件
選擇 “Qt -> Qt Rescource File”,可以創(chuàng)建一個 qrc 文件。在此基礎(chǔ)上,在 image.qrc 上右擊選擇“添加現(xiàn)有文件”,選中項目中的 logo.ico 圖標(biāo),就被成功地將圖標(biāo)添加到項目中。
接下來,哪個界面需要添加圖標(biāo),直接調(diào)用 setWindowIcon() 方法即可,例如:
setWindowIcon(QIcon(":/logo.ico"));
icon 圖標(biāo)的存儲路徑可以通過右擊項目中的 logo.ico 圖標(biāo),選擇 "Copy Path “:logo.ico” 即可獲得。
分享 Qt 程序(項目)的方式無非兩種,要么直接分享程序的源代碼,要么分享程序生成的可執(zhí)行文件。
和直接分享源碼相比,大多數(shù)人會選擇后者。但遺憾地是,Qt Creator 默認(rèn)以動態(tài)鏈接的方式生成可執(zhí)行文件,該文件無法獨(dú)立運(yùn)行,必須為其提供所需的動態(tài)鏈接庫。也就是說,只分享 Qt Creator 生成的可執(zhí)行文件是不行的,必須將運(yùn)行所需的動態(tài)鏈接庫一起分享,可執(zhí)行文件才能在他人的電腦上正常運(yùn)行。
對 Qt 程序進(jìn)行打包,指的就是找到可執(zhí)行文件運(yùn)行需要的所有動態(tài)庫文件,并將它們統(tǒng)一存放到指定的空文件夾里。本節(jié)以編寫完成的學(xué)生信息管理系統(tǒng)為例,給大家講解“怎樣在 Windows 平臺上打包一個 Qt 程序”。
打包 Qt 程序,通常選用以 release 模式生成的可執(zhí)行文件。和 debug 模式相比,release 模式生成的可執(zhí)行文件體積更小,運(yùn)行效率更快。
Qt Creator 默認(rèn)以 debug 模式生成可執(zhí)行文件,如下圖所示,可以手動修改 Qt Creator 以 release 模式生成可執(zhí)行文件:
圖 2 以 release 模式生成可執(zhí)行文件
選擇“Release”之后,再次運(yùn)行程序,生成的可執(zhí)行文件可以在下圖所示的路徑中找到:
圖 3 可執(zhí)行文件的存儲位置
找到可執(zhí)行文件之后,將其拷貝到一個空的文件夾,比如筆者將其拷貝到了新建的 D:\StuInfoFile 文件夾中,如下圖所示:
圖 4 將可執(zhí)行文件拷貝到新建的文件夾內(nèi)
此時的 StuInfoFile.exe 是無法運(yùn)行的,雙擊它系統(tǒng)會提示類似“找不到 xxx.dll”的錯誤信息。
在“開始”中找到 Qt 命令行程序并打開,如下圖所示:
圖 5 Qt命令行程序
在命令行中,先執(zhí)行 “cd D:\StuInfoFile” 命令進(jìn)入 StuInfoFile 文件夾,然后再執(zhí)行 “windeployqt StuInfoFile.exe” 命令。windeployqt 是 Qt 提供的 Windows 平臺打包工具,它能找到 StuInfoFile.exe 可執(zhí)行文件需要的所有動態(tài)鏈接庫,并將它們拷貝到當(dāng)前文件夾中。
成功執(zhí)行命令之后,StuInfoFile 文件夾內(nèi)會增加很多文件夾和文件(如下圖所示),這些都是 StuinfoFile.exe 執(zhí)行所需要的。
圖 6 打包后的 StuInfoFile 文件夾
再次雙擊 StuInfoFile.exe,如果它可以成功執(zhí)行,表明打包操作是成功的。我們可以直接將 StuInfoFile 文件夾分享給他人,只要是 Windows 平臺,都可以直接點(diǎn)擊運(yùn)行 StuinfoFile.exe 文件。
者按:本文作者為 GGV 管理合伙人童士豪。GGV 是一家在中美日三地都有投資的 VC,對跨境商業(yè)的機(jī)會很敏感。
十幾年前,多家知名的互聯(lián)網(wǎng)和科技巨頭曾經(jīng)嘗試闖入中國市場,但是它們幾乎都鎩羽而歸了。
雖然這些失敗的嘗試已經(jīng)成為了既定的事實,但是在經(jīng)歷了移動革命之后,中國市場的經(jīng)營環(huán)境已經(jīng)大為不同。
時至 2015 年,中國已經(jīng)發(fā)生了翻天覆地的變化。無論全球金融市場的表現(xiàn)如何,現(xiàn)在來自世界各地的創(chuàng)業(yè)公司都有機(jī)會真正進(jìn)入中國市場。
這種時代變遷所帶來的影響是非常深遠(yuǎn)的,在這個以發(fā)展速度評價創(chuàng)業(yè)公司(即使它們已經(jīng)準(zhǔn)備上市)的時代中,全世界最大的市場當(dāng)然會吸引世界各地最有抱負(fù)的創(chuàng)業(yè)者。
乘著中國的創(chuàng)業(yè)大潮
我們來簡單地分析一下中國創(chuàng)業(yè)環(huán)境變化的原因及形式。
首先,所有在中國股票交易所上市的公司都屬于傳統(tǒng)經(jīng)濟(jì)領(lǐng)域,其中很多都是國有企業(yè),它們身上沒有 “互聯(lián)網(wǎng)” 的基因。
其次,中國的 “本土互聯(lián)網(wǎng)” 企業(yè)都選擇在美國或香港的交易所上市,比如阿里巴巴、百度和騰訊,這些公司的發(fā)展都十分迅猛(盡管最近的市場比較不穩(wěn)定)。
第三,在中國的 13 億人口當(dāng)中有超過 6 億的智能手機(jī)用戶,以及超過 7 億的個人電腦寬帶用戶,但是只有 10%的人口擁有零售交易賬戶,也就是說這個國家的大部分人口都不會受到公開市場波動的影響。以上的事實引出了第四個要點(diǎn),中國可以開拓的市場要比十幾年前大得多,因此它會整個世界帶來了更大的機(jī)遇。
在以上因素的驅(qū)動之下,全中國上下已經(jīng)掀起了一股創(chuàng)業(yè)的熱潮。現(xiàn)在中國的 新公司注冊量增長是國內(nèi)生產(chǎn)總值增長的三倍 ,來自國內(nèi)外的風(fēng)險資本和私募基金也為 這些公司提供了發(fā)展的資金 。
另一方面,阿里巴巴、 京東 、騰訊和百度(它們都是 全球前十的互聯(lián)網(wǎng)公司 ,它們的總市值已經(jīng)超過 5000 億美元)的成功上市也證明了,任何來自中國的 年輕創(chuàng)業(yè)者都有可能在世界級的平臺上獲得成功 ——而且他們不需要擁有政府的背景支持,也不需要來自富有的家庭。
現(xiàn)在中國的環(huán)境已經(jīng)不一樣了,接下來我將為大家介紹三個例子,分析其他外國創(chuàng)業(yè)公司是如何切入這個新市場的。
Airbnb 在中國
作為在世界范圍內(nèi)最受歡迎的創(chuàng)業(yè)公司之一,Airbnb 正在破解中國市場的密碼 。
現(xiàn)今的中國擁有一個龐大而且不斷增長的中產(chǎn)階級,他們對于商品和服務(wù)有著巨大的消費(fèi)需求,這種需求可以轉(zhuǎn)化為顛覆中國傳統(tǒng)經(jīng)濟(jì)的市場動力。
例如,中國是世界上 增長最快、規(guī)模最大的境外旅游市場 ,Airbnb 抓住了這個機(jī)會。Airbnb 先 將精力集中在來自中國的境外游客 ,這是一個明智的做法,因為它的獨(dú)特競爭優(yōu)勢在于能夠在世界各地最熱門的景點(diǎn)提供特色的住宿服務(wù)。
在戰(zhàn)略方面,Airbnb 挑選了一位擁有多年亞洲實地運(yùn)營經(jīng)驗的人才擔(dān)任國際運(yùn)營副總裁。這家公司還 邀請了一批深入了解中國的私人投資者(包括紀(jì)源資本)參加它最新一輪的融資,它這樣做是為了吸取在中國市場經(jīng)營的經(jīng)驗。
Airbnb 接下來在中國的發(fā)展值得拭目以待,不過我認(rèn)為它還會在中國聘請一位專門的首席執(zhí)行官,帶領(lǐng)它繼續(xù)闖蕩這個充滿活力的市場。
Uber 在中國
雖然 Uber 的運(yùn)營手段十分出色,但它其實已經(jīng)錯失了中國市場的先機(jī)。他們在中國最大的競爭對手是滴滴快的(紀(jì)源資本參與投資的一家公司),后者的投資方包括三家亞洲互聯(lián)網(wǎng)巨頭——騰訊、阿里巴巴和軟銀。
騰訊旗下的移動即時通信平臺擁有 6 億活躍用戶,它是中國最大的移動流量入口。因為 微信和騰訊 都不會允許 Uber 接入到微信,這點(diǎn)導(dǎo)致 Uber 在中國的競爭 變得異常困難 。這點(diǎn)就像是 Facebook 和谷歌之間的恩怨 ——Facebook 也將谷歌搜索排除在自己的生態(tài)圈之外。
Uber 突破中國市場的難度非常高。交通運(yùn)輸是一個本土化程度很高的行業(yè),對于如此遼闊的一個國家來說,即使是本土企業(yè)也難以在全國范圍內(nèi)保持領(lǐng)先。
現(xiàn)在已經(jīng)出現(xiàn)了一些比 Uber 更為本土化的交通運(yùn)輸應(yīng)用和服務(wù),這點(diǎn)也讓 Uber 更難在中國脫穎而出,更不要提中國現(xiàn)在 激烈的企業(yè)競爭環(huán)境 了。
不過值得稱贊的一點(diǎn)是,Uber 已經(jīng)在中國成立了一家獨(dú)立的分公司(為了與滴滴快的競爭),這點(diǎn) 證明了 中國是 Uber 增長和擴(kuò)張的 最優(yōu)先市場 。
目前 Uber 在世界范圍內(nèi)載客量最大的三個城市都在中國。
最近的事件表明 Uber 確實是一個不好惹的對手,不過它在中國的擴(kuò)張還存在很多變數(shù),他們可能最終會轉(zhuǎn)變?yōu)?“ 雅虎-阿里巴巴 ” 的模式,也就是向本土的公司注資運(yùn)營。盡管它需要面臨諸多阻礙,但是看到這家跨國公司能夠下定決心進(jìn)入中國市場,而且付出了如此多的本地化努力也是讓人感到高興的。
游戲領(lǐng)域的 Twitch 和 Riot
游戲是另外一個在中國尋找新切入點(diǎn)的全球性領(lǐng)域。
騰訊在 2008 年 將《英雄聯(lián)盟》帶到了中國 ,它后來在 2011 年還購入了該游戲開發(fā)商 Riot Games 的大部分股份。從那以后, 騰訊已經(jīng)將《英雄聯(lián)盟》打造成世界第一的大型多人網(wǎng)絡(luò)游戲 。為了在中國取得成功,Riot 選擇了一位 中國的戰(zhàn)略合作伙伴 負(fù)責(zé)產(chǎn)品的分發(fā)和本地化工作,它還在上海成立了一支團(tuán)隊。
到了 2015 年,Riot 已經(jīng)對中國網(wǎng)絡(luò)游戲市場有了深入的認(rèn)識,在了解到 YY 語音和 QT 語音在中國的流行之后,它 決定投資位于舊金山的 Curse(紀(jì)源資本參與投資的一家公司)。在經(jīng)過與騰訊的合作和對 Curse 的投資以后,Riot 應(yīng)該能夠更好地將中國打造為一個全球性的游戲市場。
過去并不能代表未來
十年之前,包括谷歌和 Facebook 在內(nèi)的一批領(lǐng)先互聯(lián)網(wǎng)公司都曾經(jīng)嘗試進(jìn)入中國,但是它們沒有成功。為什么谷歌、雅虎、MSN 和 Facebook 在中國都失敗了呢?
西方的傳統(tǒng)觀點(diǎn)會將這個問題歸咎于中國政府的限制,但是這種想法其實是有失偏頗的——問題更多是在于這些美國巨頭缺乏本土化運(yùn)營的手段,而且當(dāng)時的中國還沒有出現(xiàn)大型的創(chuàng)業(yè)和移動技術(shù)潮流。
畢竟,谷歌(百度)、雅虎(新浪、搜狐、網(wǎng)易)、eBay(阿里巴巴)、Facebook 和 MSN (騰訊) 在中國的競爭對手都是沒有政府背景的創(chuàng)業(yè)公司,它們的資金都是來自美國的風(fēng)投公司和機(jī)構(gòu)投資者,而且它們也沒有得到中國政府的特殊關(guān)照,所以它們也一樣需要遵守中國政府對于互聯(lián)網(wǎng)產(chǎn)業(yè)法律監(jiān)管。
時至 2015 年的今天,每個人的手中都拿著智能手機(jī);朝氣蓬勃的新一代正逐漸成長為中產(chǎn)階級;創(chuàng)業(yè)已經(jīng)成為了許多中國年輕人渴望選擇的職業(yè)道路;諸如按需經(jīng)濟(jì)、共享經(jīng)濟(jì)和移動錢包等各種新概念已經(jīng)在中國生根發(fā)芽。
這些力量實際上正在顛覆中國傳統(tǒng)的線下經(jīng)濟(jì)。令人感到奇怪的一點(diǎn)是,中國市場最近的動蕩其實是源自這種新舊概念之間的沖突,這種全球和本土概念的沖突。
我們在西方媒體中了解到的中國經(jīng)濟(jì)放緩是發(fā)生在線下經(jīng)濟(jì)的。雖然中國在今年第二季度的零售銷售額增長了 10.4%,但是它的線下零售業(yè)已經(jīng)開始倒退——由于大量線下消費(fèi)者已經(jīng)轉(zhuǎn)移到網(wǎng)絡(luò)和移動商店,中國最大的商城發(fā)展商和運(yùn)營商 萬達(dá)集團(tuán)正準(zhǔn)備關(guān)閉一半的商城 。
結(jié)論
這個世界正處于迅速變化當(dāng)中,中國也不得不作出改變——而且它已經(jīng)開始改變。我在上個月的一個電視節(jié)目中說過,盡管現(xiàn)在的中國市場處于動蕩之中,但是 iPhone、星巴克和哈根達(dá)斯仍然能夠得到越來越多中國人的歡迎,這就意味著外國公司現(xiàn)在還有很多可以進(jìn)入中國的機(jī)會,爭取喜好不斷西方化的 “白富美” 消費(fèi)群體 。
現(xiàn)在已經(jīng)有越來越多與商品消費(fèi)相關(guān)的應(yīng)用和公司 (與媒體領(lǐng)域相反) 在中國取得成功,而中國則希望美國企業(yè)能夠按照中國的游戲規(guī)則進(jìn)行經(jīng)營;一些提供全球性產(chǎn)品的公司(比如 Airbnb、蘋果,但不能像是 Facebook 和谷歌這樣政治敏感的公司)將會在中國有更好的發(fā)展;對于新興的創(chuàng)業(yè)公司來說,如果它們能夠做好中國的本地化工作,并吸引一些優(yōu)秀的本土人才和投資伙伴的加入,它們將更有可能在這個世界發(fā)展最快的消費(fèi)市場站穩(wěn)腳跟。
本文來自讀者投稿,不代表 36氪 立場
“看完這篇還不夠?如果你也在創(chuàng)業(yè),并且希望自己的項目被報道,請戳這里告訴我們!”
次dnf更新,或者開啟 tgp都有可能修復(fù)文件和重置策略組。最簡單的方法就是:任務(wù)管理器-找到流氓程序-打開文件所在位置-右鍵-屬性-安全-編輯。
如圖吧,administrator 讀取和執(zhí)行。后面的框打鉤,就可以阻止騰訊游戲crossproxy和pull,還是pall的有tgp圖標(biāo)的后臺啦。后者不禁止修改的話,還是會重復(fù)喚醒cross的,頻繁loading,是其所致。
如果沒有動過的dnf,你進(jìn)行游戲的時時,看看他們是不是在后臺,有就馬上行動~
還有自動安裝tp,樓主沒遇到過。lol倒是出現(xiàn)過,策略組禁止了qt語音就好啦~
策略組