★瞭解Qt和C++的關係
★掌握Qt的信號/槽機制的原理和使用方法
★瞭解Qt的元對象系統
★掌握Qt的架構
★理解Qt的事件模型,掌握其使用的時機web
信號與槽、元對象系統、事件模型是Qt機制的核心,若是您想要掌握Qt編程,就須要對它們有比較深刻的瞭解。本章重點介紹了信號與槽的基本概念和用法、元對象系統、Qt的事件模型,以及它們在實際使用過程當中應注意的一些問題。數據庫
Qt對標準C++的擴展
標準C++對象模型爲面向對象編程提供了有效的實時支持,可是它的靜態特性在一些領域中表現的不夠靈活。事實上,GUI應用程序每每對實時性和靈活性都有着很高的要求。Qt經過其改進的對象模型在保持C++執行速度的同時提供了所須要的靈活性。 Qt相對於標準C++增添的特性主要有如下體現:
◆支持對象間通訊信號與槽機制
◆支持可查詢和可設計的動態對象屬性機制
◆事件和事件過濾器
◆國際化支持
◆支持多任務的定時器
◆支持按層檢索的對象樹
◆受保護指針
◆動態類型轉換
這些內容是Qt核心機制的重要組成部分,在下面的章節中,筆者將有選擇的向你們介紹它們。編程
1、信號與槽服務器
信號和槽機制是Qt的核心機制之一,要掌握Qt編程就須要對信號和槽有所瞭解。信號和槽是一種高級接口,它們被應用於對象之間的通訊,它們是Qt的核心特性,也是Qt不一樣於其它同類工具包的重要地方之一。在咱們所瞭解的其它GUI工具包中,窗口小部件(widget)都有一個回調函數用於響應它們觸發的動做,這個回調函數一般是一個指向某個函數的指針。在Qt中用信號和槽取代了上述機制。
1.信號(signal)
當對象的狀態發生改變時,信號被某一個對象發射(emit)。只有定義過這個信號的類或者其派生類可以發射這個信號。當一個信號被髮射時,與其相關聯的槽將被執行,就象一個正常的函數調用同樣。信號-槽機制獨立於任何GUI事件循環。只有當全部的槽正確返回之後,發射函數(emit)才返回。若是存在多個槽與某個信號相關聯,那麼,當這個信號被髮射時,這些槽將會一個接一個地被執行,可是它們執行的順序將會是不肯定的,而且咱們不能指定它們執行的順序。信號的聲明是在頭文件中進行的,而且moc工具會注意不要將信號定義在實現文件中。Qt用signals關鍵字標識信號聲明區,隨後便可聲明本身的信號。例如,下面定義了幾個信號:
signals: voidyourSignal();voidyourSignal(intx); 在上面的語句中,signals是Qt的關鍵字。接下來的一行voidyourSignal();定義了信號yourSignal,這個信號沒有攜帶參數;接下來的一行voidyourSignal(intx);定義了信號yourSignal(intx),可是它攜帶一個整形參數,這種情形相似於重載。注意,信號和槽函數的聲明通常位於頭文件中,同時在類聲明的開始位置必須加上Q_OBJECT語句,這條語句是不可缺乏的,它將告訴編譯器在編譯以前必須先應用moc工具進行擴展。關鍵字signals指出隨後開始信號的聲明,這裏signals用的是複數形式而非單數,siganls沒有public、private、protected等屬性,這點不一樣於slots。另外,signals、slots關鍵字是QT本身定義的,不是C++中的關鍵字。還有,信號的聲明相似於函數的聲明而非變量的聲明,左邊要有類型,右邊要有括號,若是要向槽中傳遞參數的話,在括號中指定每一個形式參數的類型,固然,形式參數的個數能夠多於一個。從形式上講,信號的聲明與普通的C++函數是同樣的,可是信號沒有定義函數實現。另外,信號的返回類型都是void,而C++函數的返回值能夠有豐富的類型。注意,signal代碼會由moc自動生成,moc將其轉化爲標準的C++語句,C++預處理器會認爲本身處理的是標準C++源文件。因此你們不要在本身的C++實現文件實現signal。
2.槽(slot)
槽是普通的C++成員函數,能夠被正常調用,不一樣之處是它們能夠與信號(signal)相關聯。當與其關聯的信號被髮射時,這個槽就會被調用。槽能夠有參數,但槽的參數不能有缺省值。槽也和普通成員函數同樣有訪問權限。槽的訪問權限決定了誰能夠和它相連。一般,槽也分爲三種類型,即publicslots、privateslots和protectedslots。多線程
publicslots:在這個代碼區段內聲明的槽意味着任何對象均可將信號與之相鏈接。這對於組件編程來講很是有用:你生成了許多對象,它們互相併不知道,把它們的信號和槽鏈接起來,這樣信息就能夠正確地傳遞,而且就像一個小孩子喜歡玩耍的鐵路軌道上的火車模型,把它打開而後讓它跑起來。架構
protectedslots:在這個代碼區段內聲明的槽意味着當前類及其子類能夠將信號與之相關聯。這些槽只是類的實現的一部分,而不是它和外界的接口。框架
privateslots:在這個代碼區段內聲明的槽意味着只有類本身能夠將信號與之相關聯。這就是說這些槽和這個類是很是緊密的,甚至它的子類都沒有得到鏈接權利這樣的信任。一般,咱們使用public和private聲明槽是比較常見的,建議儘可能不要使用protected關鍵字來修飾槽的屬性。此外,槽也可以聲明爲虛函數。槽的聲明也是在頭文件中進行的。例如,下面聲明瞭幾個槽:
publicslots: voidyourSlot();
voidyourSlot(intx);
注意,關鍵字slots指出隨後開始槽的聲明,這裏slots用的也是複數形式。
3.信號與槽的關聯
槽和普通的C++成員函數幾乎是同樣的-能夠是虛函數;能夠被重載;能夠是共有的、保護的或是私有的,而且也能夠被其它C++成員函數直接調用;還有,它們的參數能夠是任意類型。惟一不一樣的是:槽還能夠和信號鏈接在一塊兒,在這種狀況下,每當發射這個信號的時候,就會自動調用這個槽。 connect()語句看起來會是以下的樣子: connect(sender,SIGNAL(signal),receiver,SLOT(slot)); 這裏的sender和receiver是指向QObject的指針(是對象指針),signal和slot是不帶參數的函數名。實際上,SIGNAL()宏和SLOT()會把它們的參數轉換成相應的字符串。到目前爲止,在已經看到的實例中,咱們已經把不一樣的信號和不一樣的槽鏈接在了一塊兒。但這裏還須要考慮一些其餘的可能性。
⑴一個信號能夠鏈接多個槽
connect(slider,SIGNAL(valueChanged(int)),spinBox,SLOT(setValue(int))); connect(slider,SIGNAL(valueChanged(int)),this,SLOT(updateStatusBarIndicator(int)));
在發射這個信號的時候,會以不肯定的順序一個接一個的調用這些槽。
⑵多個信號能夠鏈接同一個槽
connect()
不管發射的是哪個信號,都會調用這個槽。
⑶一個信號能夠與另一個信號相鏈接
connect(lineEdit,SIGNAL(textChanged(constQstring&)),this,SIGNAL(updateRecord(const Qstring&)));
當發射第一個信號時,也會發射第二個信號。除此以外,信號與信號之間的鏈接和信號與槽之間的鏈接是難以區分的。
⑷鏈接能夠被移除
disconnect(lcd,SIGNAL(overflow()),this,SLOT(handleMathError()));
這種狀況較少用到,由於當刪除對象時,Qt會自動移除和這個對象相關的全部鏈接。
⑸要把信號成功鏈接到槽(或者鏈接到另一個信號),它們的參數必須具備相同的順序和相同的類型
connect(ftp,SIGNAL(rawCommandReply(int,constQString &)),this,SLOT(processReply(int,constQString&)));
⑹若是信號的參數比它所鏈接的槽的參數多,那麼多餘的參數將會被簡單的忽略掉 connect(ftp,SIGNAL(rawCommandReply(int,constQstring &)),this,SLOT(checkErrorCode(int)));
還有,若是參數類型不匹配,或者若是信號或槽不存在,則當應用程序使用調試模式構建後,Qt會在運行時發出警告。與之相相似的是,若是在信號和槽的名字中包含了參數名,Qt也會發出警告。
信號和槽機制自己是在QObject中實現的,並不僅侷限於圖形用戶界面編程中。這種機制能夠用於任何QObject的子類中。
當指定信號signal時必須使用Qt的宏SIGNAL(),當指定槽函數時必須使用宏SLOT()。若是發射者與接收者屬於同一個對象的話,那麼在connect調用中接收者參數(receiver)能夠省略。
例如,下面定義了兩個對象:標籤對象label和滾動條對象scroll,並將 valueChanged()信號與標籤對象的setNum()相關聯,另外信號還攜帶了一個整形參數,這樣標籤老是顯示滾動條所處位置的值。
QLabel *label=newQLabel; QScrollBar*scroll=newQScrollBar; QObject::connect(scroll,SIGNAL(valueChanged(int)), label,SLOT(setNum(int)));
4.信號和槽鏈接示例
如下是QObject子類的示例:
class BankAccount : public QObject {
Q_OBJECT
public:
BankAccount () {curBalance=0;}
intbalance () const {returncurBalance;}
public slots:
voidsetBalance(intnewBalance);
signals:
voidbalanceChanged(int newBalance);
private:
intcurrentBalance;
};
與多數C++類的風格相似,BankAccount類擁有構造函數、balance()「讀取」函數和setBalance()「設置」函數。它還擁有balanceChanged()信號,賬戶餘額更改時將發出此信號。發出信號時,與它相連的槽將被執行。
Set函數是在公共槽區中聲明的,所以它是一個槽。槽既能夠做爲成員函數,與其餘任何函數同樣調用,也能夠與信號相連。如下是setBalance()槽的實現過程:
voidBankAccount::setBalance(int newBalance)
{
if(newBalance!=currentBalance)
{
currentBalance = newBalance;
emit balanceChanged(currentBalance);
}
}
語句emit balanceChanged(currentBalance);將發出balanceChanged()信號,並使用當前新餘額做爲其參數。
關鍵字emit相似於「signals」和「slots」,由Qt提供,並由C++預處理器轉換成標準C++語句。如下示例說明如何鏈接兩個BankAccount對象:
BankAccount x,y;
connect(&x,SIGNAL(balanceChanged(int)),&y,SLOT(setBalance(int)));
x.setBalance(2450);
當x中的餘額設置爲2450時,系統將發出balanceChanged()信號。y中的setBalance()槽收到此信號後,將y中的餘額設置爲2450。一個對象的信號能夠與多個不一樣槽相連,多個信號也能夠與特定對象中的某一個槽相連。參數類型相同的信號和槽能夠互相鏈接。槽的參數個數能夠少於信號的參數個數,這時多餘的參數將被忽略。
5.須要注意的問題
信號與槽機制是比較靈活的,但有些侷限性咱們必須瞭解,這樣在實際的使用過程當中纔可以作到有的放矢,避免產生一些錯誤。下面就介紹一下這方面的狀況。
⑴信號與槽的效率是很是高的,可是同真正的回調函數比較起來,因爲增長了靈活性,所以在速度上仍是有所損失,固然這種損失相對來講是比較小的,經過在一臺i586-133的機器上測試是10微秒(運行Linux),可見這種機制所提供的簡潔性、靈活性仍是值得的。但若是咱們要追求高效率的話,好比在實時系統中就要儘量的少用這種機制。
⑵信號與槽機制與普通函數的調用同樣,若是使用不當的話,在程序執行時也有可能產生死循環。所以,在定義槽函數時必定要注意避免間接造成無限循環,即在槽中再次發射所接收到的一樣信號。
⑶若是一個信號與多個槽相關聯的話,那麼,當這個信號被髮射時,與之相關的槽被激活的順序將是隨機的,而且咱們不能指定該順序。
⑷宏定義不能用在signal和slot的參數中。
⑸構造函數不能用在signals或者slots聲明區域內。
⑹函數指針不能做爲信號或槽的參數。
⑺信號與槽不能有缺省參數。
⑻信號與槽也不能攜帶模板類參數。
6.小結
從QObject或其子類(例如Qwidget)派生的類都可以使用信號和槽機制。這種機制自己是在QObject中實現的,並不僅侷限於圖形用戶界面編程中:當對象的狀態獲得改變時,它能夠某種方式將信號發射(emit)出去,但它並不瞭解是誰在接收這個信號。槽被用於接收信號,事實上槽是普通的對象成員函數。槽也並不瞭解是否有任何信號與本身相鏈接。並且,對象並不瞭解具體的通訊機制。這其實是「封裝」概念的生動體現,信號與槽機制確保了Qt中的對象被看成軟件的組件來使用,體現了「軟件構件化」的思想。異步
2、元對象系統ide
Qt的元對象系統是一個基於標準C++的擴展,可以使C++更好的適應真正的組件GUI編程。它爲Qt提供了支持對象間通訊的信號與槽機制、實時類型信息和動態屬性系統等方面的功能。
元對象系統在Qt中主要有如下三部分構成:QObject類、Q_OBJECT宏和元對象編譯器moc。函數
1.元對象系統機制
Qt的主要成就之一是使用了一種機制對C++進行了擴展,而且使用這種機制建立了獨立的軟件組件。這些組件能夠綁定在一塊兒,但任何一個組件對於它所要鏈接的組件的狀況事先都不瞭解。
這種機制稱爲元對象系統(meta-objectsystem),它提供了關鍵的兩項技術:信號-槽以及內省(introspection)。內省功能對於實現信號和槽是必需的,而且容許應用程序的開發人員在運行時得到有關QObject子類的「元信息」(meta-information),包括一個含有對象的類名以及它所支持的信號和槽的列表。這一機制也支持屬性(普遍用於Qt設計師中)和文本翻譯(用於國際化),而且它也爲QtScirpt模塊奠基了基礎。
標準C++沒有對Qt的元對象系統所須要的動態元信息提供支持。Qt經過提供一個獨立的moc工具解決了這個問題,moc解析Q_OBJECT類的定義而且經過C++函數提供可供使用的信息。因爲moc使用純C++來實現它的全部功能,因此Qt的元對象系統能夠在任意C++編譯器上工做。
這一機制是這樣工做的:
⑴Q_OBJECT宏聲明瞭在每個QObject子類中必須實現的一些內省函數,如metaObject()、QMetaObject::className()、tr()、qt_metacall(),以及其它一些函數。
⑵Qt的moc工具生成了用於由Q_OBJECT聲明的全部函數和全部信號的實現。
⑶像connect()和disconnect()這樣的QObject的成員函數使用這些內省函數來完成它們的工做。
因爲全部這些工做都是由qmake和QObject類自動處理的,因此不多須要再去考慮這些事情,若是想進一步瞭解的話,也能夠閱讀一下有關QMetaObject類的文檔和由moc生成的C++源代碼文件,能夠從中看出這些實現工做是如何進行的。
2.元對象工具(moc)
Qt的信號和槽機制是採用標準C++來實現的。該實現使用C++預處理器和Qt所包括的moc(元對象編譯器)。元對象編譯器讀取應用程序的頭文件,並生成必要的代碼,以支持信號和槽機制。
qmake生成的Makefiles將自動調用moc,全部須要使用moc的編譯規則都會給自動的包含到Makefile文件中。開發人員無需直接使用moc編輯、甚至無需查看生成的代碼。
除了處理信號和槽之外,moc還支持Qt的翻譯機制、屬性系統及其擴展的運行時類型信息。好比,Q_PROPERTY()宏定義類的屬性信息,而Q_ENUMS()宏則定義在一個類中的枚舉類型列表。Q_FLAGS()宏定義在一個類中的flag枚舉類型列表,Q_CLASSINFO()宏則容許你在一個類的meta信息中插入name/value對。它還使C++程序進行運行時自檢成爲可能,並可在全部支持的平臺上工做。
元對象編譯器moc(metaobjectcompiler)對C++文件中的類聲明進行分析併產生用於初始化元對象的C++代碼,元對象包含所有信號和槽的名字以及指向這些函數的指針。
moc讀C++源文件,若是發現有Q_OBJECT宏聲明的類,它就會生成另一個C++源文件,這個新生成的文件中包含有該類的元對象代碼。例如,假設咱們有一個頭文件mysignal.h,在這個文件中包含有信號或槽的聲明,那麼在編譯以前moc工具就會根據該文件自動生成一個名爲mysignal.moc.h的C++源文件並將其提交給編譯器;相似地,對應於mysignal.cpp文件moc工具將自動生成一個名爲mysignal.moc.cpp文件提交給編譯器。
3.須要注意的問題
元對象代碼是signal/slot機制運行所必須的。用moc產生的C++源文件必須與類實現文件一塊兒進行編譯和鏈接,或者用#include語句將其包含到類的源文件中。moc並不擴展#include或者#define宏定義,它只是簡單的跳過所遇到的任何預處理指令。
3、Qt的架構
Qt的功能是創建在它所支持平臺的底層API之上的,這使得Qt很是靈活和高效。Qt使應用程序可與單平臺的應用程序配套。
Qt是一個跨平臺的框架,它使用本地樣式的API嚴格遵循每一個支持平臺中的用戶界面原則。Qt繪製了GUI應用程序所需的幾乎全部控件,而且開發人員能夠經過從新實現虛函數的方式來擴展或自定義全部這些控件。Qt的窗體可以精確模擬支持平臺的觀感,開發人員還能夠生成本身的自定義樣式,爲其應用程序提供具備鮮明特點的外觀。
Qt在它所支持的不一樣平臺中使用底層API。這與傳統的「分層」跨平臺工具套件不一樣,傳統工具套件是指在單個平臺工具套件中使用的簡單封裝(例如,在Windows中使用MFC;在X11中使用Motif)。一般,分層工具套件速度較慢,其緣由在於:庫函數的每次調用都會產生許多要通過不一樣API層的附加調用。分層工具套件每每會受到基本工具套件的功能和行爲的限制,致使應用程序中出現隱性錯誤。
Qt作到了很是專業地支持各類平臺,而且能夠充分利用各類平臺的優勢。經過使用單個源代碼樹,Qt應用程序能夠編譯成每一個目標平臺的可執行程序。儘管Qt是一個跨平臺的框架,但與許多平臺特定的工具套件相比,Qt徹底面向對象,更易於學習,更具備高效性,這使得許多開發人員在開發單個平臺時也更傾向於使用Qt。
1.X11
Qt/X11使用Xlib直接與X服務器通訊。Qt不使用Xt(XToolkit,即:X工具套件)、Motif、Athena或其餘任何工具套件。
Qt支持各類Unix:AIX®、FreeBSD®、HP-UX、Irix®、Linux、NetBSD、OpenBSD和Solaris。有關Qt所支持的編譯器和操做系統版本的最新列表信息,請訪問NOKIA公司網站。
Qt應用程序自動適應用戶的窗口管理器或桌面環境,而且在Motif、CDE、GNOME和KDE下具備桌面環境自己的觀感。這與大多數Unix工具套件相反,這些套件老是把用戶限制在套件自身觀感下。Qt全面支持Unicode。Qt應用程序自動支持Unicode和非Unicode字體。Qt將多種X字體組合在一塊兒,可顯示多語言文本。
Qt的字體處理功能十分強大,能夠在全部已安裝的字體中搜索當前字體中不存在的字符。
Qt能夠充分利用X擴展程序。對於反鋸齒字體、alpha混合字體和矢量圖形,Qt支持RENDER擴展程序。Qt還爲X輸入方法提供了現場編輯功能。Qt可使用傳統的多頭顯示適配器和Xinerama支持多個屏幕。
2.Microsoft Windows
Qt/Windows使用Win32®API和GDI用於事件和繪圖原語。Qt不使用MFC或任何其餘工具套件。特別地,Qt不使用缺少靈活性的「常見」控件上,而是採用功能更強大的可自定義的控件(若是不是特殊應用,Qt使用Windows自己的文件和打印對話框)。
使用Windows的客戶能夠在Windows98、NT4、ME、2000、XP和Vista中使用MicrosoftVisualC++®和BorlandC++來建立Qt應用程序。
Qt爲Windows版本執行運行檢查,並使用提供的最高級功能。例如,只有WindowsNT4、2000、XP和Vista支持旋轉文本;Qt則在全部Windows版本中都支持旋轉文本,並在可能的狀況下使用了操做系統自己的支持。Qt開發人員還能夠避免處理不一樣版本WindowsAPI中的差別。
Qt支持Microsoft的可訪問界面。與Windows中的常見控件不一樣,您能夠擴展Qt控件,而不會丟失Qt基本控件的可訪問或者說是固有信息。另外,咱們也能夠製做和使用自定義控件。Qt支持MicrosoftWindows下多個屏幕顯示。
3.MacOSX
Qt將Cocoa®和Carbon®API組合在一塊兒用來支持MacOSX。
Qt/Mac引入了佈局並直接支持國際化,容許採用標準化方式訪問OpenGL,並使用QtDesigner提供了功能強大的可視化設計。Qt使用事件循環處理文件和異步套接字的輸入輸出。Qt提供了穩定的數據庫支持。開發人員可使用流行的面向對象的API來建立Macintosh應用程序,該API具備綜合文檔和所有的源代碼。
Macintosh開發人員能夠在本身喜歡的平臺上建立應用程序,在其餘受支持的平臺中,只需進行簡單的重編譯,便可顯著擴大應用程序市場。Qt支持MacOSX中通用的二進制,這意味着能夠爲基於IntelCPU和PowerPC CPU的Mac建立Qt應用程序。
4、Qt的事件模型
1.事件的概念
應用程序對象將系統消息接收爲Qt事件。應用程序能夠按照不一樣的粒度對事件加以監控、過濾並作出響應。
在Qt中,事件是指從QEvent繼承的對象。Qt將事件發送給每一個QObject對象,這樣對象即可對事件作出響應。也就是說,Qt的事件處理機制主要是基於QEvent類來實現的,QEvent類是其餘事件類的基類。當一個事件產生時,Qt就會構造一個QEvent子類的實例來表述該事件,而後將該事件發送到相應的對象上進行處理。編程人員能夠對應用程序級別和對象級別中的事件進行監控和過濾。
2.事件的建立
大多數事件是由窗口系統生成的,它們負責嚮應用程序通知相關的用戶操做,例如:按鍵、鼠標單擊或者從新調整窗口大小。也能夠從編程角度來模擬這類事件。在Qt中大約有50多種事件類型,最多見的事件類型是報告鼠標活動、按鍵、重繪請求以及窗口處理操做。編程人員也能夠添加本身的活動行爲,相似於內建事件的事件類型。
一般,接收方若是隻知道按鍵了或者鬆開鼠標按鈕了,這是不夠的。例如,它還必須知道按的是哪一個鍵,鬆開的是哪一個鼠標按鈕以及鼠標所在位置。每一QEvent子類均提供事件類型的相關附加信息,所以每一個事件處理器都可利用此信息採起相應處理。
3.事件的交付
Qt經過調用虛函數QObject::event()來交付事件。出於方便起見,QObject::event()會將大多數常見的事件類型轉發給專門的處理函數,例如: QWidget::mouseReleaseEvent()和QWidget::keyPressEvent()。開發人員在編寫本身的控件時,或者對現有控件進行定製時,能夠輕鬆地從新實現這些處理函數。
有些事件會當即發送,而另外一些事件則須要排隊等候,當控制權返回至Qt事件循環時纔會開始分發。Qt使用排隊來優化特定類型的事件。例如,Qt會將多個paint事件壓縮成一個事件,以便達到最大速度。
一般,一個對象須要查看另外一對象的事件,以即可以對事件作出響應或阻塞事件。這能夠經過調用被監控對象的QObject::installEventFilter()函數來實現。實施監控對象的QObject::eventFilter()虛函數會在受監控的對象在接收事件以前被調用。
另外,若是在應用程序的QApplication惟一實例中安裝一個過濾器,則也能夠過濾應用程序的所有事件。系統先調用這類過濾器,而後再調用任何窗體特定的過濾器。開發人員甚至還能夠從新實現事件調度程序QApplication::notify(),對整個事件交付過程進行全面控制。
4.事件循環模型
Qt的主事件循環可以從事件隊列中獲取本地窗口系統事件,而後判斷事件類型,並將事件分發給特定的接收對象。主事件循環經過調用QCoreApplication::exec()啓動,隨着QCoreApplication::exit()結束,本地的事件循環可用利用QEventLoop構建。做爲事件分發器的QAbstractEventDispatcher管理着Qt的事件隊列,事件分發器從窗口系統或其餘事件源接收事件,而後將他們發送給QCoreApplication或 QApplication的實例進行處理或繼續分發。QAbstractEventDispatcher爲事件分發提供了良好的保護措施。
通常來講,事件是由觸發當前的窗口系統產生的,但也能夠經過使用 QCoreApplication::sendEvent()和QCoreApplication::postEvent()來手工產生事件。須要說明的是QCoreApplication::sendEvent()會當即發送事件, QCoreApplication::postEvent()則會將事件放在事件隊列中分發。若是須要在一個對象初始化完成之際就開始處理某種事件,能夠將事件經過QCoreApplication::postEvent()發送。
經過接收對象的event()函數能夠返回由接收對象的事件句柄返回的事件,對於某些特定類型的事件如鼠標(觸筆)和鍵盤事件,若是接收對象不能處理,事件將會被傳播到接收對象的父對象。須要說明的是接收對象的event()函數並不直接處理事件,而是根據被分發過來的事件的類型調用相應的事件句柄進行處理。
5.自定義事件
通常有下列5種方式能夠用來處理和過濾事件,每種方式都有其使用條件和使用範圍。
⑴重載paintEvent()、mousePressEvent()等事件處理器(eventhandler)
從新實現像mousePressEvent(),keyPressEvent()和paintEvent()這樣的eventhandler是目前處理event所採用的最多見的方法,這種方法比較容易掌握。
⑵重載QcoreApplication::notify()函數
這種方式可以對事件處理進行徹底控制。也就是說,當你須要在事件處理器(eventhandler)以前獲得全部事件的話,就能夠採用這個方法,可是這樣一來,由於只有一個notify()函數,因此每次只能有一個子類被激活。這與事件過濾器不一樣,由於後者能夠有任意數目而且同時存在。
⑶在QCoreApplication::instance()也即在qApp上安裝事件過濾器
這樣就可處理全部部件(widget)上的全部事件,這和重載 QCoreApplication::notify()函數的效果是相似的。一旦一個eventfilter被註冊到qApp(惟一的QApplication對象),程序裏發到其它對象的事件在發到其它的eventfilter以前,都要首先發到這個eventFilter上,不難看出,這個方法在調試(debugging)應用程序時也是很是有用的。
⑷重載QObject::event()函數
經過從新實現的event()函數,咱們能夠在事件到達特定部件的事件過濾器(eventhandler)前處理Tab事件。須要注意的是,當從新實現某個子類的event()的時候,咱們須要調用基類的event()來處理不許備顯式處理的狀況。
⑸在選定對象(Object)上安裝事件過濾器(eventfilter)
該對象須要繼承自QObject,這樣就能夠處理除了Tab和Shift-Tab之外的全部事件。當該對象用installEventFilter()註冊以後,全部發到該對象的事件都會先通過監測它的eventfilter。若是該object同時安裝了多個eventfilter,那麼這些filter會按照「後進先出」的規則依次被激活,即順序是從最後安裝的開始,到第一個被安裝的爲止。
6.事件與信號的區別
須要注意,咱們不該該混淆「事件」和「信號」這兩個概念。
⑴使用場合和時機不一樣
通常狀況下,在「使用」窗口部件時,咱們常常須要使用信號,而且會遵循信號與槽的機制;而在「實現」窗口部件時,咱們就不得不考慮如何處理事件了。舉個例子,當使用QPushButton時,咱們對於它的clicked()信號每每更爲關注,而不多關心促成發射該信號的底層的鼠標或者鍵盤事件。可是,若是要實現一個相似於QPushButton的類,咱們就須要編寫必定的處理鼠標和鍵盤事件的代碼,並且在必要的時候,仍然須要發射和接收clicked()信號。
⑵使用的機制和原理不一樣
事件相似於Windows裏的消息,它的發出者通常是窗口系統。相對信號和槽機制,它比較「底層」,它同時支持異步和同步的通訊機制,一個事件產生時將被放到事件隊列裏,而後咱們就能夠繼續執行該事件「後面」的代碼。事件的機制是非阻塞的。
信號和槽機制相對而言比較「高層」,它的發出者通常是對象。從本質上看,它相似於傳統的回調機制,是不支持異步調用的。
舉個例子,在QApplication中有兩個投送事件的方法:postEvent()和 sendEvent(),它們分別對應Windows中的PostMessage()和SendMessage(),就是是異步調用和同步調用,一個等待處理完後返回,一個只發送而無論處理完與否就返回。
在應用中,涉及到底層通訊時,每每使用事件的時候比較多,但有時也會用到信號和槽。
⑶信號與槽在多線程時支持異步調用
在單線程應用時,你能夠把信號與槽當作是一種對象間的同步通訊機制,這是由於在這種狀況下,信號的釋放過程是阻塞的,必定要等到槽函數返回後這個過程才結束,也就是不支持異步調用。
從Qt4開始,信號和槽機制被擴展爲能夠支持跨線程的鏈接,經過這種改變,信號與槽也能夠支持異步調用了,這方面的內容涉及到多線程的不少知識,讀者感興趣的話,能夠參閱《C++GUIQt4編程》中的相關內容。
5、問題與解答
問:什麼狀況下能夠斷開信號與槽的關聯?
答:有3種狀況須要斷開信號與槽的關聯:
1.斷開與某個對象相關聯的任何對象
這彷佛有點不可理解,事實上,當咱們在某個對象中定義了一個或者多個信號,這些信號與另外若干個對象中的槽相關聯,若是咱們要切斷這些關聯的話,就能夠利用這個方法,很是之簡潔。
disconnect(myObject,0,0,0);
或者
myObject->disconnect();
2.斷開與某個特定信號的任何關聯
disconnect(myObject,SIGNAL(mySignal()),0,0)
或者
myObject->disconnect(SIGNAL(mySignal()))
3.斷開兩個對象之間的關聯。
disconnect(myObject,0,myReceiver,0)
或者
myObject->disconnect(myReceiver)
在disconnect函數中0能夠用做一個通配符,分別表示任何信號、任何接收對象、接收對象中的任何槽函數。可是發射者sender不能爲0,其它三個參數的值能夠等於0。
問:Qt的元對象系統還有哪些功能?
1.QObject::metaObject()方法
它得到與一個類相關聯的meta-object。
2.QMetaObject::className()方法
在運行期間返回一個對象的類名,它不須要本地C++編譯器的RTTI(run-timetypeinformation)支持。
3.QObject::inherits()方法
它用來判斷生成一個對象類是否是從一個特定的類繼承出來,固然,這必須是在QObject類的直接或者間接派生類當中。
4.QObject::tr()andQObject::trUtf8()
這兩個方法爲軟件的國際化翻譯字符串。
5.QObject::setProperty()andQObject::property()
這兩個方法根據屬性名動態的設置和獲取屬性值。
除了以上這些功能外,它還使用qobject_cast()方法在QObject類之間提供動態轉換,qobject_cast()方法的功能相似於標準C++的dynamic_cast(),可是qobject_cast()不須要RTTI的支持,在一個QObject類或者它的派生類中,咱們能夠不定義Q_OBJECT宏。若是咱們在一個類中沒有定義Q_OBJECT宏,那麼在這裏所提到的相應的功能在這個類中也不能使用,從meta-object的觀點來講,一個沒有定義Q_OBJECT宏的類與它最接近的那個祖先類是相同的,那就是說,QMetaObject::className()方法所返回的名字並非這個類的名字,而是與它最接近的那個祖先類的名字。因此,咱們強烈建議,任何從QObject繼承出來的類都定義Q_OBJECT宏。
答:你須要在程序中重載下列函數,具體能夠查閱QtAssistant。
mousePressEvent(QMouseEvent*event)
{
//要作的事
}
keyPressEvent(QkeyEvent*event)
{
//要作的事(鍵盤)
}
★瞭解Qt和C++的關係
★掌握Qt的信號/槽機制的原理和使用方法
★瞭解Qt的元對象系統
★掌握Qt的架構
★理解Qt的事件模型,掌握其使用的時機
信號與槽、元對象系統、事件模型是Qt機制的核心,若是您想要掌握Qt編程,就須要對它們有比較深刻的瞭解。本章重點介紹了信號與槽的基本概念和用法、元對象系統、Qt的事件模型,以及它們在實際使用過程當中應注意的一些問題。
Qt對標準C++的擴展
標準C++對象模型爲面向對象編程提供了有效的實時支持,可是它的靜態特性在一些領域中表現的不夠靈活。事實上,GUI應用程序每每對實時性和靈活性都有着很高的要求。Qt經過其改進的對象模型在保持C++執行速度的同時提供了所須要的靈活性。 Qt相對於標準C++增添的特性主要有如下體現:
◆支持對象間通訊信號與槽機制
◆支持可查詢和可設計的動態對象屬性機制
◆事件和事件過濾器
◆國際化支持
◆支持多任務的定時器
◆支持按層檢索的對象樹
◆受保護指針
◆動態類型轉換
這些內容是Qt核心機制的重要組成部分,在下面的章節中,筆者將有選擇的向你們介紹它們。
1、信號與槽
信號和槽機制是Qt的核心機制之一,要掌握Qt編程就須要對信號和槽有所瞭解。信號和槽是一種高級接口,它們被應用於對象之間的通訊,它們是Qt的核心特性,也是Qt不一樣於其它同類工具包的重要地方之一。在咱們所瞭解的其它GUI工具包中,窗口小部件(widget)都有一個回調函數用於響應它們觸發的動做,這個回調函數一般是一個指向某個函數的指針。在Qt中用信號和槽取代了上述機制。
1.信號(signal)
當對象的狀態發生改變時,信號被某一個對象發射(emit)。只有定義過這個信號的類或者其派生類可以發射這個信號。當一個信號被髮射時,與其相關聯的槽將被執行,就象一個正常的函數調用同樣。信號-槽機制獨立於任何GUI事件循環。只有當全部的槽正確返回之後,發射函數(emit)才返回。若是存在多個槽與某個信號相關聯,那麼,當這個信號被髮射時,這些槽將會一個接一個地被執行,可是它們執行的順序將會是不肯定的,而且咱們不能指定它們執行的順序。信號的聲明是在頭文件中進行的,而且moc工具會注意不要將信號定義在實現文件中。Qt用signals關鍵字標識信號聲明區,隨後便可聲明本身的信號。例如,下面定義了幾個信號:
signals: voidyourSignal();voidyourSignal(intx); 在上面的語句中,signals是Qt的關鍵字。接下來的一行voidyourSignal();定義了信號yourSignal,這個信號沒有攜帶參數;接下來的一行voidyourSignal(intx);定義了信號yourSignal(intx),可是它攜帶一個整形參數,這種情形相似於重載。注意,信號和槽函數的聲明通常位於頭文件中,同時在類聲明的開始位置必須加上Q_OBJECT語句,這條語句是不可缺乏的,它將告訴編譯器在編譯以前必須先應用moc工具進行擴展。關鍵字signals指出隨後開始信號的聲明,這裏signals用的是複數形式而非單數,siganls沒有public、private、protected等屬性,這點不一樣於slots。另外,signals、slots關鍵字是QT本身定義的,不是C++中的關鍵字。還有,信號的聲明相似於函數的聲明而非變量的聲明,左邊要有類型,右邊要有括號,若是要向槽中傳遞參數的話,在括號中指定每一個形式參數的類型,固然,形式參數的個數能夠多於一個。從形式上講,信號的聲明與普通的C++函數是同樣的,可是信號沒有定義函數實現。另外,信號的返回類型都是void,而C++函數的返回值能夠有豐富的類型。注意,signal代碼會由moc自動生成,moc將其轉化爲標準的C++語句,C++預處理器會認爲本身處理的是標準C++源文件。因此你們不要在本身的C++實現文件實現signal。
2.槽(slot)
槽是普通的C++成員函數,能夠被正常調用,不一樣之處是它們能夠與信號(signal)相關聯。當與其關聯的信號被髮射時,這個槽就會被調用。槽能夠有參數,但槽的參數不能有缺省值。槽也和普通成員函數同樣有訪問權限。槽的訪問權限決定了誰能夠和它相連。一般,槽也分爲三種類型,即publicslots、privateslots和protectedslots。
publicslots:在這個代碼區段內聲明的槽意味着任何對象均可將信號與之相鏈接。這對於組件編程來講很是有用:你生成了許多對象,它們互相併不知道,把它們的信號和槽鏈接起來,這樣信息就能夠正確地傳遞,而且就像一個小孩子喜歡玩耍的鐵路軌道上的火車模型,把它打開而後讓它跑起來。
protectedslots:在這個代碼區段內聲明的槽意味着當前類及其子類能夠將信號與之相關聯。這些槽只是類的實現的一部分,而不是它和外界的接口。
privateslots:在這個代碼區段內聲明的槽意味着只有類本身能夠將信號與之相關聯。這就是說這些槽和這個類是很是緊密的,甚至它的子類都沒有得到鏈接權利這樣的信任。一般,咱們使用public和private聲明槽是比較常見的,建議儘可能不要使用protected關鍵字來修飾槽的屬性。此外,槽也可以聲明爲虛函數。槽的聲明也是在頭文件中進行的。例如,下面聲明瞭幾個槽:
publicslots: voidyourSlot();
voidyourSlot(intx);
注意,關鍵字slots指出隨後開始槽的聲明,這裏slots用的也是複數形式。
3.信號與槽的關聯
槽和普通的C++成員函數幾乎是同樣的-能夠是虛函數;能夠被重載;能夠是共有的、保護的或是私有的,而且也能夠被其它C++成員函數直接調用;還有,它們的參數能夠是任意類型。惟一不一樣的是:槽還能夠和信號鏈接在一塊兒,在這種狀況下,每當發射這個信號的時候,就會自動調用這個槽。 connect()語句看起來會是以下的樣子: connect(sender,SIGNAL(signal),receiver,SLOT(slot)); 這裏的sender和receiver是指向QObject的指針(是對象指針),signal和slot是不帶參數的函數名。實際上,SIGNAL()宏和SLOT()會把它們的參數轉換成相應的字符串。到目前爲止,在已經看到的實例中,咱們已經把不一樣的信號和不一樣的槽鏈接在了一塊兒。但這裏還須要考慮一些其餘的可能性。
⑴一個信號能夠鏈接多個槽
connect(slider,SIGNAL(valueChanged(int)),spinBox,SLOT(setValue(int))); connect(slider,SIGNAL(valueChanged(int)),this,SLOT(updateStatusBarIndicator(int)));
在發射這個信號的時候,會以不肯定的順序一個接一個的調用這些槽。
⑵多個信號能夠鏈接同一個槽
connect()
不管發射的是哪個信號,都會調用這個槽。
⑶一個信號能夠與另一個信號相鏈接
connect(lineEdit,SIGNAL(textChanged(constQstring&)),this,SIGNAL(updateRecord(const Qstring&)));
當發射第一個信號時,也會發射第二個信號。除此以外,信號與信號之間的鏈接和信號與槽之間的鏈接是難以區分的。
⑷鏈接能夠被移除
disconnect(lcd,SIGNAL(overflow()),this,SLOT(handleMathError()));
這種狀況較少用到,由於當刪除對象時,Qt會自動移除和這個對象相關的全部鏈接。
⑸要把信號成功鏈接到槽(或者鏈接到另一個信號),它們的參數必須具備相同的順序和相同的類型
connect(ftp,SIGNAL(rawCommandReply(int,constQString &)),this,SLOT(processReply(int,constQString&)));
⑹若是信號的參數比它所鏈接的槽的參數多,那麼多餘的參數將會被簡單的忽略掉 connect(ftp,SIGNAL(rawCommandReply(int,constQstring &)),this,SLOT(checkErrorCode(int)));
還有,若是參數類型不匹配,或者若是信號或槽不存在,則當應用程序使用調試模式構建後,Qt會在運行時發出警告。與之相相似的是,若是在信號和槽的名字中包含了參數名,Qt也會發出警告。
信號和槽機制自己是在QObject中實現的,並不僅侷限於圖形用戶界面編程中。這種機制能夠用於任何QObject的子類中。
當指定信號signal時必須使用Qt的宏SIGNAL(),當指定槽函數時必須使用宏SLOT()。若是發射者與接收者屬於同一個對象的話,那麼在connect調用中接收者參數(receiver)能夠省略。
例如,下面定義了兩個對象:標籤對象label和滾動條對象scroll,並將 valueChanged()信號與標籤對象的setNum()相關聯,另外信號還攜帶了一個整形參數,這樣標籤老是顯示滾動條所處位置的值。
QLabel *label=newQLabel; QScrollBar*scroll=newQScrollBar; QObject::connect(scroll,SIGNAL(valueChanged(int)), label,SLOT(setNum(int)));
4.信號和槽鏈接示例
如下是QObject子類的示例:
class BankAccount : public QObject {
Q_OBJECT
public:
BankAccount () {curBalance=0;}
intbalance () const {returncurBalance;}
public slots:
voidsetBalance(intnewBalance);
signals:
voidbalanceChanged(int newBalance);
private:
intcurrentBalance;
};
與多數C++類的風格相似,BankAccount類擁有構造函數、balance()「讀取」函數和setBalance()「設置」函數。它還擁有balanceChanged()信號,賬戶餘額更改時將發出此信號。發出信號時,與它相連的槽將被執行。
Set函數是在公共槽區中聲明的,所以它是一個槽。槽既能夠做爲成員函數,與其餘任何函數同樣調用,也能夠與信號相連。如下是setBalance()槽的實現過程:
voidBankAccount::setBalance(int newBalance)
{
if(newBalance!=currentBalance)
{
currentBalance = newBalance;
emit balanceChanged(currentBalance);
}
}
語句emit balanceChanged(currentBalance);將發出balanceChanged()信號,並使用當前新餘額做爲其參數。
關鍵字emit相似於「signals」和「slots」,由Qt提供,並由C++預處理器轉換成標準C++語句。如下示例說明如何鏈接兩個BankAccount對象:
BankAccount x,y;
connect(&x,SIGNAL(balanceChanged(int)),&y,SLOT(setBalance(int)));
x.setBalance(2450);
當x中的餘額設置爲2450時,系統將發出balanceChanged()信號。y中的setBalance()槽收到此信號後,將y中的餘額設置爲2450。一個對象的信號能夠與多個不一樣槽相連,多個信號也能夠與特定對象中的某一個槽相連。參數類型相同的信號和槽能夠互相鏈接。槽的參數個數能夠少於信號的參數個數,這時多餘的參數將被忽略。
5.須要注意的問題
信號與槽機制是比較靈活的,但有些侷限性咱們必須瞭解,這樣在實際的使用過程當中纔可以作到有的放矢,避免產生一些錯誤。下面就介紹一下這方面的狀況。
⑴信號與槽的效率是很是高的,可是同真正的回調函數比較起來,因爲增長了靈活性,所以在速度上仍是有所損失,固然這種損失相對來講是比較小的,經過在一臺i586-133的機器上測試是10微秒(運行Linux),可見這種機制所提供的簡潔性、靈活性仍是值得的。但若是咱們要追求高效率的話,好比在實時系統中就要儘量的少用這種機制。
⑵信號與槽機制與普通函數的調用同樣,若是使用不當的話,在程序執行時也有可能產生死循環。所以,在定義槽函數時必定要注意避免間接造成無限循環,即在槽中再次發射所接收到的一樣信號。
⑶若是一個信號與多個槽相關聯的話,那麼,當這個信號被髮射時,與之相關的槽被激活的順序將是隨機的,而且咱們不能指定該順序。
⑷宏定義不能用在signal和slot的參數中。
⑸構造函數不能用在signals或者slots聲明區域內。
⑹函數指針不能做爲信號或槽的參數。
⑺信號與槽不能有缺省參數。
⑻信號與槽也不能攜帶模板類參數。
6.小結
從QObject或其子類(例如Qwidget)派生的類都可以使用信號和槽機制。這種機制自己是在QObject中實現的,並不僅侷限於圖形用戶界面編程中:當對象的狀態獲得改變時,它能夠某種方式將信號發射(emit)出去,但它並不瞭解是誰在接收這個信號。槽被用於接收信號,事實上槽是普通的對象成員函數。槽也並不瞭解是否有任何信號與本身相鏈接。並且,對象並不瞭解具體的通訊機制。這其實是「封裝」概念的生動體現,信號與槽機制確保了Qt中的對象被看成軟件的組件來使用,體現了「軟件構件化」的思想。
2、元對象系統
Qt的元對象系統是一個基於標準C++的擴展,可以使C++更好的適應真正的組件GUI編程。它爲Qt提供了支持對象間通訊的信號與槽機制、實時類型信息和動態屬性系統等方面的功能。
元對象系統在Qt中主要有如下三部分構成:QObject類、Q_OBJECT宏和元對象編譯器moc。
1.元對象系統機制
Qt的主要成就之一是使用了一種機制對C++進行了擴展,而且使用這種機制建立了獨立的軟件組件。這些組件能夠綁定在一塊兒,但任何一個組件對於它所要鏈接的組件的狀況事先都不瞭解。
這種機制稱爲元對象系統(meta-objectsystem),它提供了關鍵的兩項技術:信號-槽以及內省(introspection)。內省功能對於實現信號和槽是必需的,而且容許應用程序的開發人員在運行時得到有關QObject子類的「元信息」(meta-information),包括一個含有對象的類名以及它所支持的信號和槽的列表。這一機制也支持屬性(普遍用於Qt設計師中)和文本翻譯(用於國際化),而且它也爲QtScirpt模塊奠基了基礎。
標準C++沒有對Qt的元對象系統所須要的動態元信息提供支持。Qt經過提供一個獨立的moc工具解決了這個問題,moc解析Q_OBJECT類的定義而且經過C++函數提供可供使用的信息。因爲moc使用純C++來實現它的全部功能,因此Qt的元對象系統能夠在任意C++編譯器上工做。
這一機制是這樣工做的:
⑴Q_OBJECT宏聲明瞭在每個QObject子類中必須實現的一些內省函數,如metaObject()、QMetaObject::className()、tr()、qt_metacall(),以及其它一些函數。
⑵Qt的moc工具生成了用於由Q_OBJECT聲明的全部函數和全部信號的實現。
⑶像connect()和disconnect()這樣的QObject的成員函數使用這些內省函數來完成它們的工做。
因爲全部這些工做都是由qmake和QObject類自動處理的,因此不多須要再去考慮這些事情,若是想進一步瞭解的話,也能夠閱讀一下有關QMetaObject類的文檔和由moc生成的C++源代碼文件,能夠從中看出這些實現工做是如何進行的。
2.元對象工具(moc)
Qt的信號和槽機制是採用標準C++來實現的。該實現使用C++預處理器和Qt所包括的moc(元對象編譯器)。元對象編譯器讀取應用程序的頭文件,並生成必要的代碼,以支持信號和槽機制。
qmake生成的Makefiles將自動調用moc,全部須要使用moc的編譯規則都會給自動的包含到Makefile文件中。開發人員無需直接使用moc編輯、甚至無需查看生成的代碼。
除了處理信號和槽之外,moc還支持Qt的翻譯機制、屬性系統及其擴展的運行時類型信息。好比,Q_PROPERTY()宏定義類的屬性信息,而Q_ENUMS()宏則定義在一個類中的枚舉類型列表。Q_FLAGS()宏定義在一個類中的flag枚舉類型列表,Q_CLASSINFO()宏則容許你在一個類的meta信息中插入name/value對。它還使C++程序進行運行時自檢成爲可能,並可在全部支持的平臺上工做。
元對象編譯器moc(metaobjectcompiler)對C++文件中的類聲明進行分析併產生用於初始化元對象的C++代碼,元對象包含所有信號和槽的名字以及指向這些函數的指針。
moc讀C++源文件,若是發現有Q_OBJECT宏聲明的類,它就會生成另一個C++源文件,這個新生成的文件中包含有該類的元對象代碼。例如,假設咱們有一個頭文件mysignal.h,在這個文件中包含有信號或槽的聲明,那麼在編譯以前moc工具就會根據該文件自動生成一個名爲mysignal.moc.h的C++源文件並將其提交給編譯器;相似地,對應於mysignal.cpp文件moc工具將自動生成一個名爲mysignal.moc.cpp文件提交給編譯器。
3.須要注意的問題
元對象代碼是signal/slot機制運行所必須的。用moc產生的C++源文件必須與類實現文件一塊兒進行編譯和鏈接,或者用#include語句將其包含到類的源文件中。moc並不擴展#include或者#define宏定義,它只是簡單的跳過所遇到的任何預處理指令。
3、Qt的架構
Qt的功能是創建在它所支持平臺的底層API之上的,這使得Qt很是靈活和高效。Qt使應用程序可與單平臺的應用程序配套。
Qt是一個跨平臺的框架,它使用本地樣式的API嚴格遵循每一個支持平臺中的用戶界面原則。Qt繪製了GUI應用程序所需的幾乎全部控件,而且開發人員能夠經過從新實現虛函數的方式來擴展或自定義全部這些控件。Qt的窗體可以精確模擬支持平臺的觀感,開發人員還能夠生成本身的自定義樣式,爲其應用程序提供具備鮮明特點的外觀。
Qt在它所支持的不一樣平臺中使用底層API。這與傳統的「分層」跨平臺工具套件不一樣,傳統工具套件是指在單個平臺工具套件中使用的簡單封裝(例如,在Windows中使用MFC;在X11中使用Motif)。一般,分層工具套件速度較慢,其緣由在於:庫函數的每次調用都會產生許多要通過不一樣API層的附加調用。分層工具套件每每會受到基本工具套件的功能和行爲的限制,致使應用程序中出現隱性錯誤。
Qt作到了很是專業地支持各類平臺,而且能夠充分利用各類平臺的優勢。經過使用單個源代碼樹,Qt應用程序能夠編譯成每一個目標平臺的可執行程序。儘管Qt是一個跨平臺的框架,但與許多平臺特定的工具套件相比,Qt徹底面向對象,更易於學習,更具備高效性,這使得許多開發人員在開發單個平臺時也更傾向於使用Qt。
1.X11
Qt/X11使用Xlib直接與X服務器通訊。Qt不使用Xt(XToolkit,即:X工具套件)、Motif、Athena或其餘任何工具套件。
Qt支持各類Unix:AIX®、FreeBSD®、HP-UX、Irix®、Linux、NetBSD、OpenBSD和Solaris。有關Qt所支持的編譯器和操做系統版本的最新列表信息,請訪問NOKIA公司網站。
Qt應用程序自動適應用戶的窗口管理器或桌面環境,而且在Motif、CDE、GNOME和KDE下具備桌面環境自己的觀感。這與大多數Unix工具套件相反,這些套件老是把用戶限制在套件自身觀感下。Qt全面支持Unicode。Qt應用程序自動支持Unicode和非Unicode字體。Qt將多種X字體組合在一塊兒,可顯示多語言文本。
Qt的字體處理功能十分強大,能夠在全部已安裝的字體中搜索當前字體中不存在的字符。
Qt能夠充分利用X擴展程序。對於反鋸齒字體、alpha混合字體和矢量圖形,Qt支持RENDER擴展程序。Qt還爲X輸入方法提供了現場編輯功能。Qt可使用傳統的多頭顯示適配器和Xinerama支持多個屏幕。
2.Microsoft Windows
Qt/Windows使用Win32®API和GDI用於事件和繪圖原語。Qt不使用MFC或任何其餘工具套件。特別地,Qt不使用缺少靈活性的「常見」控件上,而是採用功能更強大的可自定義的控件(若是不是特殊應用,Qt使用Windows自己的文件和打印對話框)。
使用Windows的客戶能夠在Windows98、NT4、ME、2000、XP和Vista中使用MicrosoftVisualC++®和BorlandC++來建立Qt應用程序。
Qt爲Windows版本執行運行檢查,並使用提供的最高級功能。例如,只有WindowsNT4、2000、XP和Vista支持旋轉文本;Qt則在全部Windows版本中都支持旋轉文本,並在可能的狀況下使用了操做系統自己的支持。Qt開發人員還能夠避免處理不一樣版本WindowsAPI中的差別。
Qt支持Microsoft的可訪問界面。與Windows中的常見控件不一樣,您能夠擴展Qt控件,而不會丟失Qt基本控件的可訪問或者說是固有信息。另外,咱們也能夠製做和使用自定義控件。Qt支持MicrosoftWindows下多個屏幕顯示。
3.MacOSX
Qt將Cocoa®和Carbon®API組合在一塊兒用來支持MacOSX。
Qt/Mac引入了佈局並直接支持國際化,容許採用標準化方式訪問OpenGL,並使用QtDesigner提供了功能強大的可視化設計。Qt使用事件循環處理文件和異步套接字的輸入輸出。Qt提供了穩定的數據庫支持。開發人員可使用流行的面向對象的API來建立Macintosh應用程序,該API具備綜合文檔和所有的源代碼。
Macintosh開發人員能夠在本身喜歡的平臺上建立應用程序,在其餘受支持的平臺中,只需進行簡單的重編譯,便可顯著擴大應用程序市場。Qt支持MacOSX中通用的二進制,這意味着能夠爲基於IntelCPU和PowerPC CPU的Mac建立Qt應用程序。
4、Qt的事件模型
1.事件的概念
應用程序對象將系統消息接收爲Qt事件。應用程序能夠按照不一樣的粒度對事件加以監控、過濾並作出響應。
在Qt中,事件是指從QEvent繼承的對象。Qt將事件發送給每一個QObject對象,這樣對象即可對事件作出響應。也就是說,Qt的事件處理機制主要是基於QEvent類來實現的,QEvent類是其餘事件類的基類。當一個事件產生時,Qt就會構造一個QEvent子類的實例來表述該事件,而後將該事件發送到相應的對象上進行處理。編程人員能夠對應用程序級別和對象級別中的事件進行監控和過濾。
2.事件的建立
大多數事件是由窗口系統生成的,它們負責嚮應用程序通知相關的用戶操做,例如:按鍵、鼠標單擊或者從新調整窗口大小。也能夠從編程角度來模擬這類事件。在Qt中大約有50多種事件類型,最多見的事件類型是報告鼠標活動、按鍵、重繪請求以及窗口處理操做。編程人員也能夠添加本身的活動行爲,相似於內建事件的事件類型。
一般,接收方若是隻知道按鍵了或者鬆開鼠標按鈕了,這是不夠的。例如,它還必須知道按的是哪一個鍵,鬆開的是哪一個鼠標按鈕以及鼠標所在位置。每一QEvent子類均提供事件類型的相關附加信息,所以每一個事件處理器都可利用此信息採起相應處理。
3.事件的交付
Qt經過調用虛函數QObject::event()來交付事件。出於方便起見,QObject::event()會將大多數常見的事件類型轉發給專門的處理函數,例如: QWidget::mouseReleaseEvent()和QWidget::keyPressEvent()。開發人員在編寫本身的控件時,或者對現有控件進行定製時,能夠輕鬆地從新實現這些處理函數。
有些事件會當即發送,而另外一些事件則須要排隊等候,當控制權返回至Qt事件循環時纔會開始分發。Qt使用排隊來優化特定類型的事件。例如,Qt會將多個paint事件壓縮成一個事件,以便達到最大速度。
一般,一個對象須要查看另外一對象的事件,以即可以對事件作出響應或阻塞事件。這能夠經過調用被監控對象的QObject::installEventFilter()函數來實現。實施監控對象的QObject::eventFilter()虛函數會在受監控的對象在接收事件以前被調用。
另外,若是在應用程序的QApplication惟一實例中安裝一個過濾器,則也能夠過濾應用程序的所有事件。系統先調用這類過濾器,而後再調用任何窗體特定的過濾器。開發人員甚至還能夠從新實現事件調度程序QApplication::notify(),對整個事件交付過程進行全面控制。
4.事件循環模型
Qt的主事件循環可以從事件隊列中獲取本地窗口系統事件,而後判斷事件類型,並將事件分發給特定的接收對象。主事件循環經過調用QCoreApplication::exec()啓動,隨着QCoreApplication::exit()結束,本地的事件循環可用利用QEventLoop構建。做爲事件分發器的QAbstractEventDispatcher管理着Qt的事件隊列,事件分發器從窗口系統或其餘事件源接收事件,而後將他們發送給QCoreApplication或 QApplication的實例進行處理或繼續分發。QAbstractEventDispatcher爲事件分發提供了良好的保護措施。
通常來講,事件是由觸發當前的窗口系統產生的,但也能夠經過使用 QCoreApplication::sendEvent()和QCoreApplication::postEvent()來手工產生事件。須要說明的是QCoreApplication::sendEvent()會當即發送事件, QCoreApplication::postEvent()則會將事件放在事件隊列中分發。若是須要在一個對象初始化完成之際就開始處理某種事件,能夠將事件經過QCoreApplication::postEvent()發送。
經過接收對象的event()函數能夠返回由接收對象的事件句柄返回的事件,對於某些特定類型的事件如鼠標(觸筆)和鍵盤事件,若是接收對象不能處理,事件將會被傳播到接收對象的父對象。須要說明的是接收對象的event()函數並不直接處理事件,而是根據被分發過來的事件的類型調用相應的事件句柄進行處理。
5.自定義事件
通常有下列5種方式能夠用來處理和過濾事件,每種方式都有其使用條件和使用範圍。
⑴重載paintEvent()、mousePressEvent()等事件處理器(eventhandler)
從新實現像mousePressEvent(),keyPressEvent()和paintEvent()這樣的eventhandler是目前處理event所採用的最多見的方法,這種方法比較容易掌握。
⑵重載QcoreApplication::notify()函數
這種方式可以對事件處理進行徹底控制。也就是說,當你須要在事件處理器(eventhandler)以前獲得全部事件的話,就能夠採用這個方法,可是這樣一來,由於只有一個notify()函數,因此每次只能有一個子類被激活。這與事件過濾器不一樣,由於後者能夠有任意數目而且同時存在。
⑶在QCoreApplication::instance()也即在qApp上安裝事件過濾器
這樣就可處理全部部件(widget)上的全部事件,這和重載 QCoreApplication::notify()函數的效果是相似的。一旦一個eventfilter被註冊到qApp(惟一的QApplication對象),程序裏發到其它對象的事件在發到其它的eventfilter以前,都要首先發到這個eventFilter上,不難看出,這個方法在調試(debugging)應用程序時也是很是有用的。
⑷重載QObject::event()函數
經過從新實現的event()函數,咱們能夠在事件到達特定部件的事件過濾器(eventhandler)前處理Tab事件。須要注意的是,當從新實現某個子類的event()的時候,咱們須要調用基類的event()來處理不許備顯式處理的狀況。
⑸在選定對象(Object)上安裝事件過濾器(eventfilter)
該對象須要繼承自QObject,這樣就能夠處理除了Tab和Shift-Tab之外的全部事件。當該對象用installEventFilter()註冊以後,全部發到該對象的事件都會先通過監測它的eventfilter。若是該object同時安裝了多個eventfilter,那麼這些filter會按照「後進先出」的規則依次被激活,即順序是從最後安裝的開始,到第一個被安裝的爲止。
6.事件與信號的區別
須要注意,咱們不該該混淆「事件」和「信號」這兩個概念。
⑴使用場合和時機不一樣
通常狀況下,在「使用」窗口部件時,咱們常常須要使用信號,而且會遵循信號與槽的機制;而在「實現」窗口部件時,咱們就不得不考慮如何處理事件了。舉個例子,當使用QPushButton時,咱們對於它的clicked()信號每每更爲關注,而不多關心促成發射該信號的底層的鼠標或者鍵盤事件。可是,若是要實現一個相似於QPushButton的類,咱們就須要編寫必定的處理鼠標和鍵盤事件的代碼,並且在必要的時候,仍然須要發射和接收clicked()信號。
⑵使用的機制和原理不一樣
事件相似於Windows裏的消息,它的發出者通常是窗口系統。相對信號和槽機制,它比較「底層」,它同時支持異步和同步的通訊機制,一個事件產生時將被放到事件隊列裏,而後咱們就能夠繼續執行該事件「後面」的代碼。事件的機制是非阻塞的。
信號和槽機制相對而言比較「高層」,它的發出者通常是對象。從本質上看,它相似於傳統的回調機制,是不支持異步調用的。
舉個例子,在QApplication中有兩個投送事件的方法:postEvent()和 sendEvent(),它們分別對應Windows中的PostMessage()和SendMessage(),就是是異步調用和同步調用,一個等待處理完後返回,一個只發送而無論處理完與否就返回。
在應用中,涉及到底層通訊時,每每使用事件的時候比較多,但有時也會用到信號和槽。
⑶信號與槽在多線程時支持異步調用
在單線程應用時,你能夠把信號與槽當作是一種對象間的同步通訊機制,這是由於在這種狀況下,信號的釋放過程是阻塞的,必定要等到槽函數返回後這個過程才結束,也就是不支持異步調用。
從Qt4開始,信號和槽機制被擴展爲能夠支持跨線程的鏈接,經過這種改變,信號與槽也能夠支持異步調用了,這方面的內容涉及到多線程的不少知識,讀者感興趣的話,能夠參閱《C++GUIQt4編程》中的相關內容。
5、問題與解答
問:什麼狀況下能夠斷開信號與槽的關聯?
答:有3種狀況須要斷開信號與槽的關聯:
1.斷開與某個對象相關聯的任何對象
這彷佛有點不可理解,事實上,當咱們在某個對象中定義了一個或者多個信號,這些信號與另外若干個對象中的槽相關聯,若是咱們要切斷這些關聯的話,就能夠利用這個方法,很是之簡潔。
disconnect(myObject,0,0,0);
或者
myObject->disconnect();
2.斷開與某個特定信號的任何關聯
disconnect(myObject,SIGNAL(mySignal()),0,0)
或者
myObject->disconnect(SIGNAL(mySignal()))
3.斷開兩個對象之間的關聯。
disconnect(myObject,0,myReceiver,0)
或者
myObject->disconnect(myReceiver)
在disconnect函數中0能夠用做一個通配符,分別表示任何信號、任何接收對象、接收對象中的任何槽函數。可是發射者sender不能爲0,其它三個參數的值能夠等於0。
問:Qt的元對象系統還有哪些功能?
1.QObject::metaObject()方法
它得到與一個類相關聯的meta-object。
2.QMetaObject::className()方法
在運行期間返回一個對象的類名,它不須要本地C++編譯器的RTTI(run-timetypeinformation)支持。
3.QObject::inherits()方法
它用來判斷生成一個對象類是否是從一個特定的類繼承出來,固然,這必須是在QObject類的直接或者間接派生類當中。
4.QObject::tr()andQObject::trUtf8()
這兩個方法爲軟件的國際化翻譯字符串。
5.QObject::setProperty()andQObject::property()
這兩個方法根據屬性名動態的設置和獲取屬性值。
除了以上這些功能外,它還使用qobject_cast()方法在QObject類之間提供動態轉換,qobject_cast()方法的功能相似於標準C++的dynamic_cast(),可是qobject_cast()不須要RTTI的支持,在一個QObject類或者它的派生類中,咱們能夠不定義Q_OBJECT宏。若是咱們在一個類中沒有定義Q_OBJECT宏,那麼在這裏所提到的相應的功能在這個類中也不能使用,從meta-object的觀點來講,一個沒有定義Q_OBJECT宏的類與它最接近的那個祖先類是相同的,那就是說,QMetaObject::className()方法所返回的名字並非這個類的名字,而是與它最接近的那個祖先類的名字。因此,咱們強烈建議,任何從QObject繼承出來的類都定義Q_OBJECT宏。
答:你須要在程序中重載下列函數,具體能夠查閱QtAssistant。
mousePressEvent(QMouseEvent*event)
{
//要作的事
}
keyPressEvent(QkeyEvent*event)
{
//要作的事(鍵盤) }