Qt讀書筆記

pamxy發表於2013-06-16

轉自:http://blog.renren.com/share/222668991/13981367203

Qt中的每個類,都有一個對應的同名標頭檔案,其中包含其類定義。例如要使用QApplication類,則需要在程式中新增" #include <QApplication>"

    QApplication類用於管理應用程式範圍內的資源。其建構函式需要main函式的argc和argv作為引數。

    widget被建立時都是不可見的(always created hidden)。widget中可容納其它widget。

    Qt中的widget在有使用者行為或狀態改變時會emit signal。 signal可以和slot函式連線在一起(connect),這樣當有signal被emit時,對應的slot函式會被自動呼叫。

    QWidget類的建構函式需要一個 QWidget * 指標作為引數,表示其parent widget(預設值為0,即不存在parent widget)。在parent widget被刪除時,Qt會自動刪除其所有的child widget。

    Qt中有三種Layout Manager 類: QHBoxLayout,QVBoxLayOut,QGridLayOut。基本模式是將widget新增進LayOut,由Layout自動接管widget的尺寸和位置。

    啟動Qt程式時可以通過 -style 引數改變程式的預設顯式風格。

Chapter 2 Creating Dialogs

2.1    Subclassing Dialog

Qt中所有dialog的基類是QDialog。QDialog派生自QWidget。

    Qt中所有定義了signal或slot的類,在其類定義的開始處都要使用Q_OBJECT巨集。Qt中的signal關鍵字實際上是巨集定義。類似的,slots關鍵字也是巨集定義。

    Qt所提供的類分為若干模組:QtGui,QtNetWork,QtOpenGL,QtSql, QtSvg和QtXml等。

    QObject::tr() 函式將輸入的字串轉換為其他語言(國際化)。對所有使用者可見的字串都使用tr()函式是一個良好的習慣。

    Buddy:兩個widget A和B,若A擁有快捷鍵,當使用者按下該快捷鍵時,程式的輸入焦點自動轉移到B上,則稱B是A的buddy。

    QWidget::close() 是一個slot,其預設行為是使對應的widget隱藏不可見,但並不刪除該widget。

    Layout 中可包含widget和其他layout。通過巢狀使用QHBoxLayOut、QVBoxLayOut、QGridLayOut,可以構造非常複雜的dialog。值得注意的是:layout manager 類並不屬於widget。實際上,它派生自QLayout,而QLayout又派生自QObject。

    QWidget::sizeHint() 返回一個widget()的理想大小(ideal size)。

    emit關鍵字是Qt特有的,用於產生signal。

    MOC(Meta-Object-Compiler): 對於所有使用了Q_OBJECT巨集的類,在編譯時都需要通過MOC的處理,否則會出現連結錯誤。解決該錯誤的辦法也很簡單,重新執行qmake以更新makefile,然後重新編譯。

2.2    Signal and Slot in Depth

    Signal & Slot 機制是Qt的根基。

    Slot和普通的C++類成員函式幾乎完全一致;可以是virtual的,可以被過載,可以是public、protected或private的,而且也可所以被其他成員函式直接呼叫。

    signal與slot之間的關聯可以是一對一、一對多或多對一。

    signal和signal之間也可以被關聯,此種情況與signal-slot的區別在於,當第一個sigal被emit時,第二個signal也被emit。

    可以呼叫disconnect()來解除signal 與slot之間的關聯,通常情況下很少需要顯式呼叫disconnect(),因為物件被刪除時Qt會自動移除與其相關的關聯。

    sigal-slot或signal-signal這樣的關聯,要求二者具備相同的引數列表;若signal比slot中的引數多,多餘的引數會被忽略。

    思維定勢:signal-slot機制只能用於widget。實際上signal-slot機制是由QObject實現的,並不僅侷限於GUI程式設計,可以用於任何QObject子類。

2.3    Rapid Dialog Design

    使用Qt Designer建立的form最終被轉換為C++程式碼。

    qmake工具能夠檢測到interface file(*.ui files),並呼叫uic,即Qt的user interface compiler。uic將.ui檔案轉換為C++程式碼,並存放在形式為ui_xxx.h的檔案中。該檔案中給出了dialog對應類的完整定義,幷包含一個 setupUi()成員函式,用於初始化form。

    注意,由uic建立的這個類未派生自任何Qt class。

    Qt的 parent-child機制是由QObject實現的。當建立一個物件時若指定了parent,則parent將該物件新增至其 childern list。當parent被刪除時,Qt會遍歷其childern list並刪除每個child,該過程會遞迴進行。這一機制極大的簡化了記憶體管理,降低了記憶體洩露的風險——程式設計師只需顯式的刪除通過new建立並且沒有parent的物件。

    對於widget,parent還有一層附加的意義:chidl widget 是顯示在parent widget的範圍之內的。如果刪除parent widget,不僅child widget從記憶體中被釋放,在螢幕上也會消失。

    QDialog::accept() 將dialog的返回值設為QDialog::Accepted(值為1),而QDialog::reject()將返回值設為QDIalog::Rejected(值為0)。

2.5    Dynamic Dialogs

    Dynamic Dialog指的是程式在執行時根據.ui檔案建立的dialog。這樣的dialog不是通過uic將.ui轉換為C++程式碼,而是在執行時使用QUiLoader類裝載.ui檔案。

    可以使用QObject::findChild<T> ()來訪問form的child widget。

    要使用QUiLoader,需要在Qt程式的.pro檔案中新增以下內容: CONFIG  += uitools

    Dynamic dialog允許在不重新編譯程式的前提下更改form的佈局。

Chapter 3 Creating Main Windows

3.1    Subclassing QMainWindow

應用程式的主視窗是通過建立QMainWindow的派生類來完成的。QMainWindow和QDialog一樣,都是派生自QWidget。

    closeEvent()是由QWidget提供的一個虛擬函式,在使用者關閉視窗時會被自動呼叫。

    setCentralWidget()將某個Widget設定為主視窗的central widget, 而central widget意味著在顯示時會佔據主視窗的中央位置。

    Qt下的GUI 程式設計支援多種圖形格式。可以使用多種方式為應用程式提供影象,最常見的包括:

    1).    將影象儲存在檔案中,執行時載入之。

    2).    在原始碼中include XPM檔案(XPM檔案也是合法的C++檔案)。

    3).    利用Qt的資源機制。

    Qt的資源機制比之執行時載入更方便,並對所有支援的影象格式都能良好工作。

    為了利用Qt的資源機制,需要建立一個資原始檔,並在.pro檔案中對應新增一行來對資原始檔進行標識。例如:

    RESOURCES= spreadsheet.qrc

    資原始檔本身採用了簡單的XML格式。它被編譯程式序的可執行檔案,因此不會被丟失。在對資源進行定位時,使用路徑字首":/",例如“ :/images/icon.png "。資源本身可以是任何型別的檔案。

3.2    Creating Menus and Toolbars

    Qt通過引入Action這一概念簡化了對menu和toolbar的程式設計。一個Action可以被新增到任意數量menu和toobar中。

    在Qt中對menu和toolbar的程式設計涉及到三個步驟:

    1).    建立並設定Action

    2).    建立menu,並在其中新增Action

    3).    建立toolbar,並在其中新增Action

    Action的建立是通過QAction類來實現的,對每個Action,可以為其設定accelerator,parent,shortcut key, 可見性以及status tip等屬性,並可以通過呼叫connect()為ACtion設定被觸發要執行的操作。

    QTableWidget的基類QAbstraceItemView提供了selectAll()這個slot。

    QApplication類提供了aboutQt()這個slot,可以通過全域性變數qApp(一個型別為QApplication *的指標)來使用之。

    在Qt中,menu由QMenu類的例項表示。而Qmenu是要被放入QMenuBar之中的。函式QMainWindow::menuBar()返回一個型別為QMenuBar * 的指標。QMenuBar::addMenu()根據指定文字建立一個QMenu widget並將其新增進MenuBar中。QMenu::addAction() 則為Menu新增Action。

    任意Qt Widget都可以具備相關的一系列QAction。通過呼叫QWidget::addAction() ,可以為Widget新增Action。這一特性可用來建立上下文選單。

3.3    Setting Up the Status Bar

    QMainWindow::statusBar() 返回一個指向status bar的指標;status bar 在statusBar()第一次被呼叫時被建立。

3.4    Implementing The Menu

    QMessageBox::Defalut修飾符使得被修飾的Button成為預設Button,而QMessage::Escape修飾符則使得Esc鍵自動觸發被修飾的Button。

    QMessageBox::warning()用於彈出提示對話方塊。該函式屬於Qt提供的static convenicence function

static convenience function

    QFileDialog::getOpenFileName() 可用於從使用者處獲得檔名——該函式彈出一個檔案選擇對話方塊,要求使用者選擇一個檔案,並返回檔名,或者在使用者選擇"Cancel"時返回空字串。該函式的第一個引數是其parent widget。對於dialog和其他widget,parent-child關係的意味是不完全相同的。一個dialog永遠是一個獨立的視窗,但是如果它擁有parent,則預設在parent之上居中顯示。

    當使用者發出關閉視窗的操作時,Qwidget::close() 這個slot會被呼叫,該slot向對應的widget傳送close event。重新實現QWidget::closeEvent()能夠攔截這個event,以便確定是否真的要關閉視窗,防止誤操作。

    每個QWidget都有一個windowModified屬性,在視窗文件被修改時應該被設為True,否則被設為false。

    QString::arg() 函式將字串中編號最低的"%n"用引數進行替換,並返回替換後的字串。

    每個Action都可以擁有一個型別為QVariant的關聯資料。

    Qt中的qobject_cast<T>() 機制對於動態庫也可以正常工作。

3.5    Using Dialog

    modeless window——one that runs independently of any other windows in the application

    對於modeless dialog ,當其被彈出時,可能處於三種情況:

    1).    這是該對話方塊第一次被啟用

    2).    該對話方塊之前曾被啟用,但使用者又將其關閉

    3).    該對話方塊之前曾被啟用,而且仍可見

    show() 將一個隱藏視窗變為可見,而activateWIndow()則將視窗的狀態變為active。

    model window——pops up when invoked and blocks the application,preventing any other processing or interactions until it is closed.

    一個dialog若是用show()來啟用,則是modeless dialog;若通過exec()來啟用,則是model dialog。此外,還可以呼叫setModel()來設定dialog的顯示模式。

    QDialog::exec() 的返回至在dialog被確認時為true,否則為false。

    在棧上建立 model dialog是一種良好的程式設計慣例,因為在使用完後就不再需要,而model dialog會在作用域結束後自動被銷燬。

    由於多數應用程式的About box 都是高度雷同的,Qt中提供了一個方便的static convenicence function QMessage::about(),該函式和QMessageBox::warning()很相似。

3.6    Storing Setting

    Qt中是通過QSettings類來將應用程式的設定資訊儲存到平臺相關的位置——windows下存入登錄檔中,unix中存在文字檔案中。

    QSettings的建構函式包含兩個引數,分別是organization's name 和 application's name ,Qt使用這兩個引數來對應用程式的設定資訊進行定位。

    QSettings以key-value pair的形式儲存資訊。

3.7    Multiple Documents

    要想實現多文件程式,首先必須要通過new在堆上建立主視窗,而不是在棧上建立主視窗。

    QAplication::closeAllWindows() 這個slot完成的操作是關閉應用程式所有的視窗,除非其中某個視窗拒絕了close event。程式設計師不需要擔心未儲存的修改,因為這會由QWidget::closeEvent()負責處理。

    通過在MainWindow的建構函式中呼叫setAttribute()函式來設定Qt::WA_DeleteOnClose屬性,可以要求Qt在視窗被關閉時將其自動銷燬

    Qt在其可用所有平臺上都支援SDI和MDI程式的建立。

3.8    Splash Screnns

    在Qt中為程式新增splash screen非常簡單,可通過QSplashScreen類來實現。

    通常情況下,與splash screen相關的程式碼都放在main()中,出現在呼叫QApplication::exec()之前。

Chapter 4 Implementing Application Functionality

4.1    The Central Widget

QMainWindow的中央區域可以被任何型別的widget佔據。

4.2    Subclassing QTableWidget

QTableWidget會自動建立QTableWidgetItem來儲存使用者的輸入。

    QTableWidgetItem類並不是widget,而是一個純粹的data class。

    QTabeWidget::setItermProtype()可以設定在獲得使用者輸入的情況下自動建立哪種cllass。

4.3    Loading and Saving

    QFile & QDataStream

    QFile的解構函式負責將開啟的檔案關閉。

    QDataStream類具有很強的通用性,可作用於QFile,QBuffer,QProcess,QTcpSocket,QUdpSocket。

    Qt還提供了一個QTextStream類用於專門讀寫文字檔案。

4.6    Subclassing QTableWidgetItem

每個QTableWidgetIterm中可儲存若干資料,這是通過個QVariant來實現的。每一個QVariant物件都以某個role來儲存某一類資料,常用的role有Qt::EditRole和Qt::DiaplayRole。

    QVarinant物件可以存放多種型別的變數值,並提供向其他型別轉型的函式介面。

    使用預設建構函式建立的QVariant物件被視為invalid variant。

 

 

Chapter 5. Creating Custom Widgets

使用者自定義的控制元件可以通過繼承現有的Qt控制元件實現,也可以直接從QWidget繼承來實現

5.1    Customizing Qt Widgets

5.2    Subclassing QWidget   

通過對QWidget進行派生,並重新編寫其部分event handler來進行繪圖和響應使用者操作,程式設計師可以實現對widget的外觀和行為的完全控制。

Qt的內建Widget如QLabel、QPushButton、QTabelWidget等,就是以這種方式實現的。

    巨集Q_PROPERTY()用來為widget宣告和新增自定義屬性。

    每個屬性的定義都對應一個資料型別(任何被QVarinat支援的型別都可以),一個read function以及可選的write function。

    對於包含自定義屬性的類,Q_OBJECT和Q_PROPERTY()這兩個巨集都是必備的。

    QImage類以一種硬體無關的方式儲存影象資訊。

    Qt中提供了兩個型別用於儲存色彩資訊:QRgb和QColor。

    QRgb其實是一個typedef,用於存放32-bit的畫素資訊。

    QColor則是一個提供了許多介面函式的類,在Qt中廣泛的用於儲存色彩。

    QWidget::update()函式用於對widget進行強制性的重繪。

    QWidget::updateGeometry()用於告知包含該widget的layout:該widget的size hint已發生變化,layout會自動進行調整。

    通過呼叫QWidget::update()和QWidget::repaint(),可以強制性的產生一個 paint event,兩者的卻別在於repaint()導致立即重繪,而update()只是將一個paint event放入event queue中。

    如果對update()進行連續多次呼叫,Qt會將連續的paint event壓縮合併為一個paint event,以防止影象抖動。

    每個widget都擁有一個palette,用於設定widget中在什麼情況下使用什麼色彩,如背景色、文字色等。

    widget的palette由三個color group組成:active ,inactive ,disabled。

    QWidget::palette()以QPalette的形式返回widget的palette,而clolor group則通過列舉型別QPalette::ColorGroup指定

5.3    Intergrating  Custom Widgets with Qt Designer

要像在Qt Designer中使用自定義widget的話,必須要讓Qt Designer能夠了解到它們的存在。

    有兩種機制:promotion approach &plugin approach

    promotion approach 很容易也很省時,但缺點是自定義widget的自定義屬性在Qt Designer中是不可見和不可訪問的,而使用plugin approach時則不存在這些問題。

    plugin approach要求建立一個Qt Designer 可以在執行時載入的plugin library,以用於建立widget的例項。由於Qt的MOC機制,Qt Designer可以動態獲取widget的property list。

    要使用plugin approach ,首先要對QDesignCustomWidgetInterface進行派生,並重寫某些虛擬函式。

    Q_INTERFACES()巨集用於告知Qt該類實現了哪個interface。

    在實現plugin class的原始檔尾部,必須使用Q_EXPORT_PLUGIN2()巨集使得該plugin對Qt Designer可見、可用。該巨集第一個引數是plugin的名字,第二個引數是實現該plugin的class name。

5.4    Double Buffering

    QWidget::style()返回用於繪製該widget時所使用的style。Qt中的style都是QStyle的派生類。同一應用程式中的 widget一般都使用相同的style,然而可以呼叫QWidget::setStyle()來進行widget層次的特別設定。

Chatper 6 Layout Management

6.1    Laying Out Widgets on a Form

Qt提供的的基本的Layout Manager包括:QHBoxLayout,QVBoxLayout,QGridLayout和QStackLayout。

    Qt中其它能完成Layout management功能的類包括 QSplitter,QScrollArea,QMainWindow和QWorkspace。

    Qt中管理child widget的layout共有三種方式:absolute positioning, manual layout和layout managers。

    Absolute positioning:即由程式設計師通過hard-coded的形式管理child widget的位置和尺寸。

    Manual Layout:child widget的位置依然由程式設計師通過hard-coded的方式確定,而尺寸與父視窗的大小成一定比例,而不是完全的hard-coded。這種方式通過對form的resizeEvent()進行再實現來對child widget的定位。

    最重要的三個Layout Manger是QHBoxLayout,QVBoxLayout,QGridLayOut,他們都是派生自QLayout

    QGridLayout的使用略微有些複雜,它工作在一個由Cell組成的二維grid上。對於QGridLayout,為其新增widget的方式如下:

    layout->addWidget(widget,row ,colum,rowSpan,columnSpan)

    其中widget為待新增的child widget, row和clomun確定該widget所佔據空間中左上角那個Cell的位置座標,rowSpan和columnSpan則指定widget的大小,這兩個引數的的預設值為1。

    addStretch()向Layout Manager中新增“佔位符”。

    每個widget都有自己的size policy,由其告知layout 系統如何處理該widget外形上的stretch或是shrink。Qt中widget的size policy是通過QSizePolicy類來表示的。每個QSizePolicy由水平和豎直兩組size policy組成,最常見的值包括:

    Fixed    Minimum    Maximum    Prefered    Expanding

    除了上述兩組size policy外,QSizePolicy中還儲存水平和豎直方向的stretch factor,該值用來表明在form尺寸擴充套件時widget隨之擴充套件的比率。

6.2    Stacked Layouts

QStackLayout 類可以管理多個page,但每次只顯示其中之一,而將其他page向使用者隱藏。

    QStackLayout類本身是不可見的。

    為了方便起見,Qt包含有QStackedWidget類,即一個內建了QStackedLayout的QWidget。

6.3    Splitter

類QSplitter是一個能包含其他widget的widget。QSplitter中包含的widget按順序排列,並被splitter handle相互分隔開。

    QSplitter通過建構函式中的引數來來決定是水平方向還是豎直方向。

    不同於前面介紹的Layout Mangener們只是負責處理widget的layout而自身沒有視覺化的表示,QSplitter派生自QWidget,因此可以同其它widget一樣的被使用。

    QSplitter類提供了儲存自身狀態的兩個函式:savestate()和restorestate()。

6.4    Scrolling Areas

    QScrollArea類提供了1個可滑動的viewport和2個滑動條。

    QScrollArea的使用方法是呼叫其提供的setWidget()函式,將希望為其新增滑動條的widget新增。QScrollArea自動將新增進來的widget的parent設定為其viewport,而其viewpoint可通過QScrollArea::viewport()進行訪問。

    QScrollArea的多數功能是通過繼承QAbstraceScrollArea類而獲得的。而諸如QTextEdit和 QAbstractIterView這樣的類是派生自QAbstractScrollArea的,因此不需要將其用QScrollArea類包裹起來以獲得scroll bar。

6.5    Dock Widgets and Toolbars

   在Qt中,dock widget是通過QDockWidget類來實現的。

    每個dock widget都有自己的title bar。

    從Qt4開始,toolbar擁有自己專屬的顯示空間,而不再是如之前的版本中允許dock widget與其分享。

    對一個widget呼叫setAllowedArea()可以指定允許在那些dock areas上放置該widget。

6.6    Multiple Document Interface

    Qt中,編寫MDI程式是通過使用QWorkspace類,將其作為程式的central widget,並把每個文件視窗都作為QWorkspace的child。

    Qt程式的命令列引數中與Qt相關的引數,會由QApplication的建構函式負責自動移除掉,不會傳遞給Qt程式的main()函式。

Chapter 7 Event Processing

Qt中大多數event都是作為對使用者操作的響應而產生的,但也有一些是系統內部獨立生成的。

    在使用Qt進行程式設計時,通常很少需要考慮event,因為Qt中的widget會在有重要事件發生時emit signal。Event在我們要編寫自定義widget或是要修改現有Qt widget的特性時,則變得很重要。

    不要在概念上將event和signal兩個概念混淆。當程式設計師操縱使用widget時,signal是需要關注的物件;而當程式設計師需要實現一個widget時,event則是需要關注的物件。

    例如,當使用QPushButton時,我們更關注其提供的clicked() signal 而不是導致該signal被QPushButton emit的低層次的滑鼠或鍵盤event。但是如果我們要自己實現一個類似於QPushButton的類,那就輪到我們編寫程式碼來處理滑鼠和鍵盤操作,並在必要時emit clicked() signal。

7.1    Reimplementing Event Handles

Qt中,每個event都是一個派生自QEvent的物件。Qt中包含超過100種的event,每種event有一個enum value進行標識。

    QEvent::type()返回event type。

    所有event都是通過其對應類中的event()函式,來向物件傳送通告的;這個event()函式繼承自QObject。QWidget中 event()的實現是將最常見型別的event轉發給對應的event handlers,例如mousePressEvent(),keyPress-Event,painEvent()。

    鍵盤event對應的event handler是keyPressEvent()和keyReleaseEvent()。

    QKeyEvent::modifiers()

    Tab鍵和Shift+Tab鍵的處理有些特殊,它們不是由keyPressEvent()負責處理,而是在QWidget::event()中進行處理,而且發生在呼叫keyPressEvent()之前,處理方式是將輸入焦點按照鏈中的順序向後或向前傳遞。

    實現key binding的更高層次的實現方式是利用QAction。QAction類內部使用了QShortCut類來實現key binding。

    QObject::startTimer() 用於建立定時器,並返回相應的ID。QObject支援多個定時器同時存在。

    killTimer()用於銷燬定時器,引數為定時器的ID。

    Timer event位於底層,實現定時功能更簡單的方式是使用QTimer類,QTimer會定期的emit timeout() siganl。

7.2    Installing Event Filter

    Qt的evnet model中很強大的一個特性,就是可以設定某個物件來監控另外一個物件,後者所有的event在對其可見前都要先通過前一個物件的監控和處理。

    Qt中設定event filter涉及到兩步操作:

    1.    在要被監控的物件中呼叫installEventFilter() 來完成對監控者的註冊。

    2     在監控者的eventFilter() 中對被監控者的event進行處理。

    Qt的event model中,一個event若未得到處理(event handler的返回值為false),則Qt會負責將該event向上傳遞,即交由其parent負責處理

    Qt中對event的處理可以分為下面5個層次:

    1.    對某個特定的event handler進行重實現

    2.    對Widget中的QObject::event()進行重實現,這樣在event被傳遞給特定的event handler之前就得到了處理。

    3.    為某個物件安裝event filter

    4.    為QApplication object安裝filter,這樣可以監控應用程式中所有物件收到的所有event,在進行debug是非常有用。

    5.    派生QAapplication的子類,並對notify()進行重實現。Qt呼叫notify()來傳送event的。這是捕獲所有event的唯一方法。

7.3    Staying Responsive During Intensive Processing

在完成耗時操作的同時,還要保證程式能夠對使用者操作正常相應,常見的解決機制是多執行緒。

    一種簡單的解決方案是在耗時操作的過程中有規律的呼叫QApplication::processEvnets()。該函式通知Qt處理pending events,處理結束後再將控制權返回至呼叫者。

    Qt中的進度對話方塊是由QProgressDialog類來實現的。

    除了使用多執行緒和進度對話方塊外,還存在一種完全不同的處理耗時操作的方法:操作只在程式空閒(無使用者互動)時再進行,而不是立刻開始執行。

    這種方法可以利用0-milisecond timer來實現,每次定時器觸發時,檢查是否有pending event,若無則繼續好事操作,若有則處理event,完成與使用者的互動。

Chapter 9 Drag and Drop

9.1    Enabling Drag and Drop

預設情況下,QTextEdit這個widget接受來自於其他程式的文字拖拽的;如果使用者將一個檔案拖拽至其上,它會將檔名插入顯示文字。可以呼叫setAcceptDrops()來允許或禁止接受拖拽。

    dragEnterEvent() ,該函式在使用者將一個物件拖(drag)至widget之上時被呼叫,其引數為QDragEnterEvent型別的指標。

    預設情況下,widget不接受使用者的拖拽行為;若對該指標呼叫acceptProposedAction(),則是告知Qt允許該widget接受使用者的拖拽行為,Qt會通過改變滑鼠形狀來提示使用者。

    dropEvent() ,該函式在使用者將一個物件拽(drop)至widget之上時被呼叫,引數為QDropEvent型別的指標。

    QWidget類還提供了dragMoveMent()和dragLeaveEvent()這兩個函式,但對於大多數應用而言不需要對其進行再實現。

    mousePressEvent(): 滑鼠被按下時該函式被呼叫

    mouseMoveEvent() :滑鼠保持按下的狀態且移動時,該函式被呼叫。

    QDrag類使用QMimeData類來儲存與拖拽操作相關的資訊。

9.2    Supporting Custom Drag Types

可以從以下三種機制中進行選擇:

    1.    在源這一邊呼叫QMimeData::setData(),將資訊儲存在QByteArray中,而在接受者這一方,呼叫QMimeData::data()將資訊提取出來。

    2.     對QMimeData進行派生,在子類中對formats()和retrieveData()這兩個函式進行重新實現,來處理自定義資料。

    3.     如果拖拽動作發生在一個應用程式的內部,那麼可以對QMimeData進行派生,將資訊儲存在該子類中。

    QMimeData::formats()返回其支援的MIME型別列表。

    QMimeData::retrieveData()將某個指定MIME型別的資料以QVariant的形式返回。QMimeData所提供 text()、html()、urls()、data()等介面函式,都是依靠retrieveData()來完成底層操作的。

9.3    Clipboadr Handling

Qt中通過QApplication::clipboard()來獲得對QClipboard的指標。對系統clipboard的寫操作通過 setText(),setImage或setPixmap()完成,而讀操作則通過text(),image()和pixmap()來完成。

    QClipboard::setMimeData()

    QClipboard::MimeData()

    QClipboard::supportsSelection() 在X11平臺下返回true,其他環境下返回fasle。

    如果希望每當clipboard中的內容發生變動時收到通知,可以利用Qt提供的QClipboard::dataChanged()這個slot

Chapter 10    Item View Classes

MVC機制:Model-View-Controller

    Qt中提供一種模仿MVC的model/viewer機制。

    Qt中的delegate這個抽象概念與Controller略微不同,它負責為item的生成和編輯提供良好的控制。   

    Qt為每種型別的view都提供了預設的delegate,這對於大多數應用程式已經足夠了,通常情況下程式設計師不需要考慮delegatd的問題。

    可以為一個model註冊兩個或更多的view,Qt自動保持多個view之間資料的同步和一致性,當資料在某個view中被修改後,會自動在其他相關view中反映出來。

    大多數情況下,程式向使用者提供的item的數量並不龐大,因此可以簡單的使用Qt內建的item view clas(QListWidget,QTableWidget和QtreeView),而沒有必要使用Qt提供的model/view 機制。但對於成員數量很大的資料集,採用model/view機制則是明智的選擇。   

10.1    Using Item View Convenience Classes

    QListWidget

    QListWidget中包含多個roles,每個都與一個QVariant變數關聯。最常用的roles由Qt::DisplayRole,Qt::EditRole,Qt::IconRole

對於上述role,Qt都提供了方便的介面函式用於讀寫資料。程式設計師還可以對role進行自定義,通過Qt::UserRole或更大的數值進行標識。

    預設情況下QListWidget是隻讀的,需要呼叫QAbstractItemView::setEditTriggers()為其設定能引發編輯操作的動作。

    QTableWidget

    預設情況下QTableWidget是允許編輯的,可以呼叫QAbstractItemView::setEditTriggers(QAbstractItemView::NoEditTriggers)來禁止編輯。

    QtreeWidget

    預設情況下,QtreeWidget是隻讀的。

10.2    Using Predefined Models

Qt提供的預定義model有以下幾種:

    QStringListModel                  儲存一組字串

    QStandardItemModel            儲存任意層次結構的資料

    QDirModel                           對檔案系統進行封裝

    QSqlQueryModel                   對SQL的查詢結果集進行封裝

    QSqlTableModel                    對SQL中的table進行封裝

    QSqlRelationalTableModel      對帶有foreign key的SQL table進行封裝

    QSortFilterProxyModel            對另一個model執行sort and/or filter

    model中存放的每項資料都有相應的"model index",由QModelIndex類來表示。

    每個index由三個部分構成:row,column和表明所屬model的指標。對於一維的list model,column部分永遠為0。

    在model/view 機制中,對資料的操作都是通過model執行的,而model負責保證在資料發生變動時view自動更新。

    QDirModle::mkdir()——建立資料夾的工作可以通過QDir類來完成,不過QDirModel提供了工作於QModelIndex之上更方便的函式。

    不同於其它model,QSortFilterProxyModel對一個已有的model進行封裝,並完成資料在底層modle和view之間的傳遞。

    setSourceModel()

10.3    Implementing Custom Models

    model中的每項資料都有對應的index和一組稱為“role”的屬性,其中最常用的有Qt::DisplayRole和Qt::EditRole。

    對於list和table這兩類model,其中每個元素的parent都是root,即表示為一個invalid QModelIndex。

    而對於tree model,某些元素的parent為root,而有些元素的parent為model中的其它元素。

    Qt中提供了幾種model 基類,包括 QAbstractListModel,QAbstractTableModel和QAbstractItemModel ,其中QAbstractItemModel是另外兩者的基類,用於支援範圍很廣的modles,包括具備遞迴層次結構的;而 QAbstractListModel用於支援一維資料集,QAbstractTableModel用於支援二維資料集。

    實際上要建立只讀的自定義model的話,並不是件困難的事情。

    要自定義只讀的table model的話, 需要重新實現rowCount(),columnCount()和data()這三個函式。

    createIndex() 用於建立並返回一個model index

    flags()被model用於表示可以對資料執行的操作(例如,是否可編輯),從QAbstractTableModel繼承而來的預設實現是返回Qt::ItemSelectable | Qt::ItemIsEnabled

    qDeleteALl()對一個含有指標的容器進行迭代,並對其中每個指標元素執行delete操作。

10.4    Implementing Custom Delegates

view中的每個item是由delegate負責顯示和編輯的,在大多數情況下,view預設的delegate足夠滿足使用者需求。

    setItemDelegate() 為view指定其delegate。

    QItemDelegate & QAbstractItemDelegate

    要提供一個允許編輯的自定義delegate,我們必須對createEditor(),setEditorData(),setModelData()進行重新實現,而且還必須重新實現paint()以改變item的顯示。

    QTimeEdit & QTime

    當使用者開始編輯操作時,view會呼叫createEditor()來建立一個Editor,然後呼叫setEditorData()來用item的當前值對Editor進行初始化。

Chapter 11 Container Class

    Qt提供的容器類的最大優點在於平臺無關性和隱式共享特性

11.1    Sequentail Containers

QVector<T> array-like data structure(在尾部插入資料時效率很高,而在中間和頭部插入資料時開銷很大)。

QVector提供了[]運算子

    QVector可以用<<運算子代替append()函式。

    QVector中的基本型別及指標被初始化為0。

QLinkedList<T>

    QLinkedList不提供[]運算子,所以必須通過迭代器來對其進行遍歷。

QList<T>  array-list:綜合了QVector<T>和QLinkedList<T>最重要的優點:    支援[]運算子

    在頭部或尾部的插入/刪除操作很迅速,而尺寸在1000以下時,在中間的插入/刪除操作也很迅速。

    通常情況下,QList是最合適的通用型容器。

QStringList: QList<QString>的子類,在Qt中的API中被廣泛使用

QStack<T> 和QQueue<T>是Qt提供的兩個convenience subclasses,QStack<T>實際上是一個額外提供了push(),pop()top()介面的QVector,而 QQueue<T>實際是一個額外提供了enqueue()和dequeue()和head()的QList。

    容器中可以放置的類必須擁有default constructor、copy constructor 和 assignment operator(顯式定義或由編譯器生成)

    注意,派生自QObject的類不符合上述要求,因為其不具備copy constructor和assignment operator;解決方法是在容器中儲存物件指標而不是物件本身。

    容器中所存放的元素本身也可以是容器,即可以巢狀——不過需要注意將連續的尖括號用空格分隔開,以免編譯器誤認為>>運算子。

Iterator

    Qt支援兩種風格的迭代器——Java-style和STL-style

    Java-style的迭代器更容易使用,而STL-style的迭代器可以同Qt和STL中的演算法聯合使用,更為強大。

    Java-style Iterator   

    每個sequential容器類,都有兩個Java-style的迭代器型別:只讀迭代器和讀寫迭代器。

    在使用Java-style的迭代器時,要清楚的第一件事情就是:迭代器並不直接指向容器中的元素,而是指向元素之前或之後的位置。迭代器被初始化時指向容器中第一個元素之前;若迭代器的右側有元素存在,hasNext()函式返回true;next()函式返回位於迭代器右側的元素,並將迭代器向右方移動一個元素的位置;hasPrevious()和previous()函式執行反方向的操作。

    remove()函式總是刪除最近一次被跳過的那個元素。

    setValue()函式總是對最近一次被跳過的那個元素執行更新操作

    insert()函式在迭代器當前指向的位置處插入新元素,並將迭代器指向新元素及其後續元素之間的位置。

    STL-style Iterator

    每個sequential容器類,都有兩個STL-style的迭代器型別:Container<T>::iterator和Container<T>::const_iterator。

    容器的begin()函式返回一個指向容器中頭部元素的iterator,而end()返回指向容器中尾部元素之後位置的iterator;

    在容器為空時,begin()和end()的結果相同。

    通常通過呼叫isEmpty()來檢查容器是否為空,而不是通過比較begin()和end()的結果。

    可以對STL-style的iterator使用+、-、*這三個運算子,類似於指標的用法。

    某些Qt函式的返回值是容器類;如果需要使用STL-style的迭代器來對這樣的返回值進行遍歷,必須儲存返回值的一個副本,並在副本上完成遍歷,否則會可能會導致所謂的"dangling iterator"。

    注意,若使用java-style的只讀迭代器,在這種情況下會隱式的完成複製的工作,保證迭代器總是在副本上進行遍歷操作。

implicit sharing(copy on write)

    Qt中的implicit sharing機制的美妙之處在於它鼓勵程式設計師在返回物件時採用傳值這種簡明的方式而不是引用或指標。

    STL與此相反,鼓勵程式設計師使用non-const引用來傳遞vector以避免將函式返回值的複製開銷。

    Qt中所有的容器都採用了implicit sharing機制;此外很多其他類QByteArray,QBrush,QFont,QImage,QString也採用了該機制——這保證這些類在以傳值方式進行傳遞時有很高的效率,無論是作為引數還是函式返回值。

    在Qt提供的implicit sharing機制下,對vector或list執行只讀操作時,採用at()而不是[]運算子是一個更好的選擇。

    類似的,儘可能的使用constBegin()和constEnd()以避免不必要的拷貝操作。

foreach syntax

    foreach在進入迴圈體時自動複製容器的副本並在此副本上進行迭代,因此如果迭代過程中有通過迭代器對容器的修改操作的話,並不會影響迴圈的進行,迴圈結束後容器的內容也不會發生變化。

    當然,如果在foreach迴圈中直接使用[]運算子對容器進行寫操作的話,容器內容自然會發生變化。

    foreach中支援break和continue語句

11.2    Associative Containers

QMap

QMap中的key-value對是升序排列的

    插入和刪除操作中都可以使用[]運算子,其下標為key;為避免建立不必要的空值,推薦用vlaue()而不是[]從QMap中取值。

    QMap<K,T>中的K和T除了要求具備預設建構函式、拷貝建構函式和賦值運算子外,K還必須支援operator <,因為這樣才能實現前面提到的升序排列。

    keys() & values()

    QMap的特性是單值;QMultiMap<K,T>則支援同一關鍵字下多值的存在,插入操作由insertMulti()完成

QHash

    QHash提供的介面和QMap很相似

    QHash<K,T>中的K要符合的額外要求:支援operator ==,並且K可用全域性函式qHash()來計算hash value

    QHash通常是單值的,而QMultiHash則通過insertMulti()支援多值插入。

QCache<K,T> & QSet<K>

    遍歷associative containr的最簡單方法是使用Java-style的迭代器

    foreach syntax也可用於assocaitive container

11.3    Generic Algorithms

    標頭檔案<QAlgorithms>中宣告瞭一組全域性模板函式,用於實現作用於容器的基本演算法;多數演算法都通過STL-style的迭代器來完成。

    STL標頭檔案<algorithm>中的函式,即可作用於Qt容器,也可作用於STL容器。

    qFind(),qBinaryFind(),qFill(),qCopy(),qSort(),qStableSorg(),qDeleteAll(),qSwap()

    需要注意的是,qDeleteAll()只對包含指標的容器有意義,該函式將釋放所有物件,但並不刪除容器中的指標。

11.4    Strings,Byte Arrays,and Variants

QString,QByteArray和QVariant這三個類和容器類有很多相似之處,在某些場合下可作為容器類的替代品;和容器類一樣,這三個類也應用了implicit sharing 機制

QString   

    QString中存放的是16-bit的Unicold值。

    從概念上,QString可以看成是QVector<QChar>。

    QString提供+/+=運算子以及append()函式用於合併字串

    QString提供的sprintf()函式,與C++標準庫中的sprintf支援相同的引數格式。

    QString提供的arg()函式,是比sprintf()更好的選擇,因為它保證型別安全,支援unicode。

    QString::number(): number->string

    QString::setNum(): number->string

    反向的轉換介面:toInt(),toLongLong(),toDouble()等,這些函式都有一個可選引數——bool型別的指標,若轉換失敗則將該bool變數置為true,否則置false.

    mid()函式返回指定區間內的子串;left()和right()函式則分別返回左子串和右子串

    QString的indexOf()函式可用於文字匹配,返回所匹配子串的起始下標;匹配失敗時返回值為-1。

    startsWith()和endsWith()函式可用於判斷字串的首部和尾部是否符合某種模式。

    QString在進行比較時是大小寫敏感的;當所比較的字串是使用者可見時,使用localeAwareCompare()通常是正確的選擇。

    toLower() & toUpper()

    replace() & remove() & insert()

    trimmed() & simplified()——這兩個函式用於消去字串中的whitesapce(spaces,tabs,newlines等)

    QString::split()將一個字串分割為子串,並返回由這些子串們組成的一個QStringList。

    QStringList中的所有元素可以通過join()函式組成一個新的字串,join()的引數在合併時會被插入相鄰元素中間。

    在大多數情況下,由const char * 至QString的轉換是自動的。

    反方向的轉換,可通過toAscii()或toLatin1()來完成;這些函式返回一個QByteArray,其可以通過QByteArray::data或QByteArray::constData()來轉換為const char * ;

    為了簡化轉換,Qt提供了一個qPrintable()巨集用於完成與toAscii()以及constData()相同的操作。

QByteArray

    QByteArray提供的API與QString的很相似。

    QByteArray的用處在於儲存原始2進位制資料及8-bit編碼的字串。

   

    通常選擇QString而不是QByteArray來儲存文字資訊,因為QString支援Unicode。

    QByteArray會自動在最後一個元素之後補上‘\0',這樣使得將QByteArray傳遞給需要const char *的函式變得很容易。

    QByteArray支援'\0',允許儲存任意的2進位制資料。

QVariant

    QVariant類可用於存放很多Qt型別的值,並且還可以存放容器。

    QVariant在 item view class,database model 和QSetting中被廣泛的使用著。

    利用QVariant和巢狀,可以建立非常複雜的資料結構。

    QVariant的便利性是以效能和程式碼的可讀性為代價的。

    QVariant被Qt的meta-object system所使用,因此是Qtcore module的組成部分。

    QVariant也可以支援使用者自定義的資料型別,前提是該型別具有defalut constructor和copy constructor。要實現對使用者自定義型別的支援,需要使用巨集Q_DECLARE來註冊該型別。

全域性函式:qVariantFromValue(),qVariantValue<T>(),qVariantCanConvert<T>()

 

Chapter 12 Input/Output

Qt通過QIODevice類,對支援塊讀寫的裝置進行了強有力的抽象和封裝,從而提供了良好的I/O支援。

    Qt中提供的QIODevice的子類包括QFile,QTemperorayFile,QBuffer,QProcess,QTcpSocket和QUdpSocket.

    QProcess,QTcpSocket和QUdpSocekt屬於sequential device,即資料只能被訪問一次,且只能按照順序讀取;而QFile、QTemporaryFile和QBuffer屬於random-access device,資料可以被訪問任意次,且可以從任意位置開始讀取;

    除了以上的device class,Qt還提供了兩個可用於讀寫任何裝置的高層資料流類:用於二進位制資料的QDataStream和用於文字的QTextStream。這兩個類負責處理位元組序和文字編碼等問題,保證執行在不同平臺或不同國家的Qt程式能正確讀取彼此的檔案。這使得Qt的I/O類比起C++標準庫中的I/O類更方便——它將這些問題留給了程式設計師來處理。

    QProcess允許程式設計師呼叫外部程式並通過標準輸入流、標準輸出流和標準錯誤流與其進行通訊。預設情況下,程式之間的通訊是非同步的,但也可以在某些操作上阻塞。

12.1    Reading and Writing Binary Data

Qt中載入和儲存二進位制資料最簡單的方法就是使用QFile來開啟檔案,並通過QDataStream物件來訪問檔案內容。

    QDataStream物件的version number直接影響著Qt中的資料型別以何種方式表示和記錄。C++的基本型別保證總是以同一種方式表示和記錄,不受verson number的影響。

    在使用QDataStream時,需要保證在讀檔案和寫檔案時使用相同的number version。

    若QDataStream被用來單純讀寫C++基本型別的資料,那麼沒有必要呼叫setVersion()來設定version number

    QDataStream的預設位元組序是big-endian,這可以通過呼叫setByteOrder()改變。

    使用Qt時,通常沒有必要顯式執行關閉檔案的操作,因為QFile在銷燬的時候會自動執行檔案的關閉操作。

    可以呼叫flush()來強制完成資料的寫操作。

    QDataStream儲存資料的方式能保證可以無縫的將其再次讀出,例如,一個QByteArray物件的存放形式是一個32-bit的計數值後跟資料本身。

    可以使用readRawData()和writeRawData()來讀寫原始位元組。

    使用QDataStream讀資料時的錯誤處理是很簡單的,status()函式返回當前狀態,包括QDataStream::Ok、QDataStream::ReadPastEnd和QDataStream::ReadCorruptData。

    當有錯誤發生時,>>運算子的返回值總是0值或是空值。

    為自定義型別提供>>和<<運算子有很多好處:可以對包含該自定義型別的容器使用QDataStream;可以將該自定義型別的資料通過QVariant儲存起來,這需要先呼叫qRegisterMetaTypeStreamOperators<T>()巨集。

    如果希望一次性讀/寫檔案,可以放棄QDataStream與QIODevice子類的聯合使用,而用QIODevice提供的write()和readAll()介面來直接進行讀寫操作。

    QIODevice()提供的peek()函式返回下一個待讀取的資料,而不改變讀寫指標,該介面對於random-access device 和sequentail device 都適用。

    對於random-access,可以使用seek()來指定讀寫指標的位置。

12.2    Reading and Wrinting Text

Qt提供了QTextStream來讀寫文字格式的檔案,如plain text,HTML,XML,source code等。

    QTextStream負責完成Unicode與系統本地編碼之間的轉換,並自動處理不同作業系統之間換行符不同表示方式上的轉換(如在windows下是\r\n,在linux是\n)。此外還自動完成C++基本數字型別與字串之間的相互轉換。

    QTextStream使用QChar作為基本的資料單元。

    寫文字資料非常容易,然而讀文字則可能非常具有挑戰性,因為文字資料在本質上是具有歧義性的。

    預設情況下,QTextStream使用本地編碼來進行讀寫操作;可以呼叫setCodec()改變之,如stream.setCodec("UTF-8");

    Qt仿照C++的I/O庫為QTextStream也提供了stream manipulators。

    同QDataStream一樣,QTextStream在一個QIODevice之上進行操作;除此之外還可以在QString上進行操作,這種情況下不需要為流設定編碼格式,因為QString總是Unicode。

    如果只讀寫ASCII字符集和Latin-1字符集的檔案,可以直接使用QIODevice提供的API進行讀寫,而不用使用QTextStream。通常這並不是一個好主意,因為這不利於國際化和後期維護。

    如果真的需要直接向QIODevice寫文字,在使用open開啟該QIODeveice時必須指定QIODevice::Text標誌;該標誌的作用在於告知QIODevice,在寫文字資料時,在windows平臺上將所有\n轉換為\r\n;而在讀文字資料時,在所有平臺上忽略\r。

12.3    Traversing Directories

    QDir提供了一種平臺無關的方法用於獲取檔案資訊以及遍歷目錄。

    QDir::entryList()    其第二個參數列明要讀取目錄下的那些條目——檔案(QDir::Files)還是子目錄(QDIr::Dirs)等。

    QDir::separator()   返回當前平臺上的目錄分割符QDir在所有平臺上都將'/'視為目錄分割符,在windows平臺上還額外的識別'\'。

    QDir::currentPath()  返回程式當前目錄的絕對路徑

    QDir::homePath()   返回使用者的主目錄路徑

    QFileInfo類允許程式設計師獲取檔案的屬性資訊,如大小,訪問許可權,所有者及各種時間戳等。

12.4    Embedding Resources

Qt允許在程式的可執行檔案中嵌入二進位制或文字檔案,這是通過Qt的資源機制實現的。

12.5    Inter-process Communication

    QProcess類允許執行外部程式並與其通訊。該類非同步工作,在後臺完成相應的工作來保證UI對使用者操作的正常響應,在外部程式終止或有資料產生時emit signal來通知本程式。

    QProcess::start()用於傳遞必要的引數,並啟動外部程式。

    靜態函式QProcess::execute()會執行一個外部程式並在外部程式結束之前保持阻塞狀態。

    QTemporaryFile::open()在無引數時以讀寫模式開啟檔案。

    QTemporaryFile在物件生存期結束時會自動刪除臨時檔案。

    當QProcess以同步模式使用時,不需要建立signal-slot連線。

    如果需要比靜態函式execute()精度更高的控制,可以改用下面的方法:

    首先建立一個QProcess物件,然後對其呼叫start(),之後呼叫QProcess::waitForStarted()強制進入阻塞狀態,直至外部程式順利啟動為止,之後再呼叫QProcess::waitForFinished(),阻塞直至外部程式結束為止。

Chapter 13 Databases

QtSql模組提供了一個平臺無關、資料庫無關的訪問SQL資料庫的介面。

    Qt中的每個資料庫連線用一個QSqlDatabase物件來表示;Qt使用不同driver來和各種不同資料庫的API進行通訊。

    QSqlQuery提供了直接執行任意SQL語句的特性;此外還提供了兩個高層次的無需SQL命令的資料庫介面:QSqlTableModel和QSqlRelationalTableModel

13.1    Connecting and Querying

    在執行SQL命令前,必須先建立好同資料庫的連線。

    靜態函式QSqlDatabase::addDatabase()用於建立一個新的QSqlDatabase物件,函式的第一個引數指定了Qt該選擇哪個Driver來訪問資料庫。

    對QSqlDatabase物件設定好host name,database name ,username和password後,需要呼叫open()函式建立到資料庫的連線。

    一旦到資料庫的連結建立好後,就可以通過QSqlQuery::exec()來執行底層資料庫所支援的任意SQL語句了。

    QSqlQuery::next()返回查詢結果集中的下一行,而QSqlQuery::value()則返回當前行中的某一項的值,以QVariant的形式返回。

    可以使用QSqlQuery::isActive()來檢查SQL語句的執行是否出現錯誤。

Placeholder

    QSqlQuery::prepare()

    QSqlQuery::bindValue() or QSqlQuery::addBindValue()

    QSqlQuery::exec()

    Qt支援資料庫中transaction(事務)這個概念。transaction()用於啟動transaction,而commit()或rollback()用於結束transaction。

    靜態函式QSqlDatabase::database(),返回指定連線所對應的QSqlDatabase物件。

    QSqlDatabase::driver() 返回該連線底層所使用的dirver

    QSqlDatabase::hasFeature()可用來查詢底層資料庫是否支援某項特性。

    Qt允許在一個程式中建立多個資料庫連線,這種情況下在執行SQL語句時,需要為QSqlQuery物件的建構函式傳入要執行該語句的資料庫對應的QSqlDatabase物件。

    與QSqlQuery相比,QSqlTableModel提供了一個更高層次、更抽象的介面,可以避免使用原始的SQL命令。

    QSqlTableModel::record() & QSqlTableModel::value()

    QSqlTableModel::insertRow() & QSqlTableModel::setData()

    QSqlTableModel::submitAll() ,與其他model不同,在使用QSqlTableModel時,必須呼叫submitAll()來強制所有的修改都寫入資料庫。

    當需要處理外來鍵foreign key時,需要使用QSqlRelationalTableModel而不是QSqlTableModel。

    對於使用了SQL相關類的應用程式,需要在對應的.pro中新增下面一行:"QT     +=sql",這樣在連結時會將QtSql庫鏈入。

Chapter 14. Networking

14.1    Wrinting FTP Client

QFtp是Qt提供的封裝了ftp協議的一個類。

    Qftp的所執行的操作是非同步完成的,這保證了FTP命令在執行過程中UI處於可相應狀態。

    當程式不需要鏈入QtGui庫時,可以在main()中建立QCoreApplication物件而不是QApplication物件。

    QCoreApplication::argurments() 函式以QStringList的形式返回程式的命令列引數,其中第一個引數為程式名,並且所有與Qt相關的引數例如-style都已經被移除掉了。

    QUrl是Qt提供的一個用於從url中提取各種資訊的一個高層介面。

    提交的FTP命令被排隊,並在Qt的event loop中被執行;QFtp物件在它處理完所有請求後會emit done(bool) 這個signal,其中型別為bool的參數列明是否有錯誤發生。

    QFtp所封裝的Ftp命令包括 connectToHost(),login(),close(),list(),cd(),get(),put(),remove(),dir(),mkdir(0,rmdir() 和rename()。 這些函式都返回一個標記命令的ID。

    TransferMode()可用於改變傳輸模式,預設為passive;TransferType()用於改變傳輸型別,預設為binary。

    此外,還可以通過rawCommand()來執行任意標準ftp命令,如 ftp.rawCommand("SITE CHMOD 755 fortune");

    QFtp在開始執行每條FTP命令時都會emit commandStarted(int)這個signal,並在每條FTP命令完成時emit commandFinished(int,bool) 這個signal,其中int 引數是命令對應的ID,而bool引數則表示是否有錯誤發生。

    QFtp在每當ftp連線的狀態發生改變時會emit stateChanged() 這個signal,這裡的狀態包括QFtp::connecting、QFtp::connected、QFtp::LoggedIn等。

    每當命令佇列變為空時,QFtp都會emit done(bool)這個signal。

    當有錯誤發生時,Qftp自動將命令佇列清空。

    若要使用QFtp,需要在專案的.pro檔案中新增下面一行:" Qt  +=network"

    listInfo(),每當QFtp執行list()命令得到一個目錄項時,都會emit 這個signal

    QFtp的get()函式在呼叫時可以不給出要寫的裝置,這種情況下QFtp會在有新資料可用時emit readyRead()這個signal ,程式設計師可以呼叫read或readAll()這兩個介面用於讀資料。

14.2    Writing HTTP Clients

    與Ftp協議相對應,Qt為Http協議提供了QHttp類。

    QFtp和QHttp在介面和特性上有很多相似之處。

    QHttp同樣是非同步工作模式。

    QHttp在開始執行request命令時emit requestStarted(int)這個signal,而在request操作結束時emit requestFinished (int,bool)這個signal,其中int引數和bool引數的含義與QFtp中的類似。 當有錯誤發生時,請求佇列被自動清空。與QFtp相同,QHttp也提供了readyRead()訊號和read()、readAll()這兩個介面函式。

14.3    Writing TCP Client-Server Applications

    QTcpSocket和QTcpServer這兩個類用於編寫Tcp客戶端和服務端

    基於TCP的應用程式或者是line-oriented,或者是block-oriented。

    QTcpSocket通過對QAbstractSocket的繼承而成為QIODevice的子類,因而可以使用QDatatStream或QTextStream來對其進行讀寫。

    QTcpSocket在有資料可讀時會emit readyRead()這個signal。

    Qt中提供的forever syntax等同於 for( ; ; )

    QTcpServer類允許接受外來TCP連線,每當檢測到外來TCP連線請求時,會自動呼叫QTcpServer::incomingConnection()函式,引數為標識socket ID的int型變數。

    QTcpSocket::listen()用於完成監聽工作。

    QTcpSocket提供的canReadLine()和readLine()這兩個函式對於line-oriented應用程式提供了很大的便利。

14.4    Sending and Receiving UDP Datagrams

    不同於QTcpSocket,QUdpSocket不支援主機名而只支援主機地址。

    將主機名轉換為IP地址,有兩種選擇,一是使用靜態函式QHostInfo::fromName(),該函式是阻塞的;二是使用靜態函式QHostInfo::lookupHost(),該函式是非阻塞的。

    QUdpSocket::bind()

    readyRead() signal

    QUdpSocket將收到的datagram排隊,並允許客戶一次訪問一個datagram。

 

Chapter 15 XML

Qt中的QtXml模組提供了兩組不同的API用於讀取XML文件

    SAX(Simple API for XML):通過virtual function直接嚮應用程式報告"parsing event“。

    DOM(Document Object Model):將XML文件轉換為樹型結構。

    SAX接近底層,速度更快;DOM更便於使用。

15.1    Readin XML with SAX

    SAX是事實上的讀取XML文件的標準API,Qt中的SAX類仿照了Java中的SAX2的實現。

    Qt提供了一個SAX-based的non-validateing型別的XML解析器QXmlSimpleReader。解析器在讀取文件時,會呼叫其註冊的handler class中的virtual function。

    Qt為QXmlSimpleReader提供了若干handler class,對於大多數應用,只需要使用QXmlContentHandlerr和QXmlErrorHandler。

    Qt提供了一個對程式設計師很方便的QXmlDefaultHandler,它對所有的handler class都進行了派生,並對所有virtual function提供了簡單的實現。

    要使用QtXml庫,需要在.pro檔案中加入下面一行: "QT   +=xml"

15.2    Reading XML with DOM

    DOM是由W3C制訂的解析XML的標準API,Qt提供了一個non-validating型別的DOM level 2級別的實現,可用於XML文件的讀寫以及其他操作。

    DOM在記憶體中將XML檔案表示為樹的形式。

    QDomDocument::setContent(),設定要讀取的XML文件。

15.3    Writing XML

    Qt中存在兩種生成XML文件的方法

    1).    構建一個DOM tree,並對其呼叫save()

    2).    手動輸出XML格式

    兩種方式之間的選擇獨立於讀取XML文件時選擇SAX或是DOM

    預設情況下,QDomDocument::save()生成文件時採用UTF-8編碼格式。

Chapter 16 Providing Help

QWidget::setToolTip()用於為Widget設定相應的tip文字。    同樣,QAction::setToolTip()為Action設定相應的tip文字;若沒有顯式的為Action設定tip文字,Action會自動的使用action text。

    setStatusTip(),該函式為Widget和Action新增 status tip。

    QTextBrowser類能解析大量HTML標籤,可用於顯示基於HTML的文字內容

    Qt Assistant支援索引和文字搜尋功能,可以很好的用於提供線上幫助

    要使用Qt Assistant,必須在程式中書寫必要的程式碼讓Qt Assistant能察覺到文件的所在。

    Qt程式與Qt Assisant之間的通訊是由QAssistantClient這個類來負責的;該類屬於一個單獨的類庫,要使用該類庫,需要在.pro中新增下面一行: CONFIG     +=assistant

    QAssitantClient類的建構函式以檔案路徑作為首個引數,用於確定 Qt Assistant可執行檔案的位置。

Chapter 17 Internationalization

Qt4為國際化內建了很多良好的支援:

    1).    Qt的API介面和內部實現均是基於Unicode的

    2).    Qt的文字引擎支援所有的non-Latin的書寫系統,包括阿拉伯、中日韓、希伯來、印度、泰國等。

    3).    Qt的layout 引擎為right-to-left風格的阿拉伯文和希伯來文提供了相應的支援。

    4).    某些語言在輸入文字時需要使用專用的輸入法,QLineEdit和QTextEdit可以和系統中安裝的任意輸入法協調工作。

    Qt提供了用於文字翻譯的GUI工具:Qt Linguist,以及兩個輔助命令列程式 lupdate和lrelease。

    大多數程式都是在啟動階段就根據使用者的locale setting載入合適的translation file,然而某些情況下使用者要求能夠在執行時實時的切換介面語言。

17.1    Working With Unicode

    QString以Unicode來儲存字串,QString中的每個字元都是一個16-bit的QChar而不是8-bit的char。

    對QString中的某個位置進行賦值操作,可以通過字元方式來確定新值,也可以通過數值方式來確定,例如要將型別為QString的str的首字元設為'A',可以有下面兩種方式:

    str[0]='A';

    str[0]=QChar(0x41);

    基於QChar之上的程式設計不同於基於char。要獲得一個QChar變數的編碼值,對其呼叫unicode()函式;要獲得一個QChar變數對應的 ASCII或Latin-1編碼值,對其呼叫toLatin1(),若原來QChar中存放的是non-latin字元,該函式返回'\0'。

    Qt為QChar類提供了基於Unicode的判斷函式,如isPrint(),isSpace(),isLetter(),isNumber()等,其工作與C++標準庫提供的isalpha(),isdigit(),isspace()類似。

    Qt負責將Unicode編碼的QString正常顯示,並在需要和其他系統進行通訊時轉換為相關的編碼格式。

    預設情況下QTextStream使用系統本地的8-bit編碼格式(可通過呼叫QTextCodec::codecForLocale()檢視)讀寫文字檔案,對於美國和西歐,這通常意味著使用Latin-1。

    可以呼叫QTextStream::setCodec()來自定義讀寫檔案時所使用的編碼,如 stream.setCodec("UTF-16");

    UTF-16格式與QString的記憶體表示一致,因此使用UTF-16讀寫Unicode字串速率會很高,缺點是在儲存純ASCII資料時會有較大的開銷。

    setCodec()的引數是一個合適的QTextCodec物件,由其負責完成Unicode和本地編碼之間的轉換。

    QTextCodec::codecForName():根據引數中給出的編碼名稱返回對應的QTextCodec物件。

    在讀文字檔案時,預設情況下QTextStream能夠自動檢測Unicode編碼(依據是0xFFFE,這個Unicode byte order mark);可以通過呼叫setAutoDetectUnicode(false)來關閉該特性。

    預設情況下,Qt將傳遞給函式tr()的引數視為Latin-1編碼的字串;可以呼叫靜態函式QTextCodec::setCodecForTr()來改變該預設設定,而自定義編碼方式。注意,這必須在第一次呼叫tr()之前就完成。

    然而即便如此,程式碼中的其它顯式字串仍會被解釋為Latin-1字串;解決方法之一是利用QTextCodec物件的toUnicode()函式,也可以呼叫QText::setCodecForCStrings()來告知Qt在const char*與QString之間進行轉換時採用何種編碼。

17.2    Making Applications Translation-Aware

    要實現程式的多語言化,需要完成兩件事情:

    1).    確保程式中每個使用者可見的字串都被tr()處理。

    2).    確保程式啟動時載入translation file(.qm)。

    tr()是在QObject中定義的靜態函式,並且在每個使用了Q_OBJECT巨集的子類都被overridden。

    tr()返回字串的一個翻譯版本,如果存在的話;否則將輸入引數原樣返回。

    要準備transaltion file,需要使用Qt提供的lupdate,該工具將程式碼中所有出現在tr()中的可見字串提取出來並生成待翻譯的translation file,這樣的translation將傳送給翻譯者來完成翻譯工作。

    呼叫tr()函式的一般形式為

    Context::tr(sourceText,comment);

    其中Context是tr()所屬的類,comment是可選引數,用於為翻譯者提供附加資訊。

    當在一個全域性函式中呼叫tr()時,必須顯式的指明相應的Context(類)。

    QApplication::translate() 函式完成與tr()函式相同的工作。

    tr()和QApplication::translate()完成雙重工作:一方面供lupdate從中提取使用者可見字串,另一方面從translation file中提取字串的對應翻譯。

    儘管對一個字串變數而不是字串常值呼叫tr()並不是個好主意,然而實際上也是可以作到的,這需要在將字串常值賦值給某個字串變數時呼叫QT_TR_NOOP()巨集,該巨集不進行任何操作,只是為lupdate提供標識。巨集QT_TRANSLATE_NOOP完成同樣的工作,不同之處在於引數中可以指定context,這對於初始化類外變數很有用。

    如何能確保程式設計師在編寫程式碼時將所有使用者可見字串用tr()包裹起來而不出現遺漏呢?可以通過在包含任何Qt標頭檔案之前定義預定義符號 QT_NO_CAST_FROM_ASCII這個來告訴Qt禁止從const chat * 到QString的自動轉換;通常在.pro檔案中新增下面一行:

 “DEFINES    +=QT_NO_CAST_FROM_ASCII"來實現這一定義。

    這樣就強迫每個字串常量在使用時必須被tr()或QLatin1String()函式包裹從而轉換為QString,這取決於該字串常量是否需要被翻譯。

    靜態函式QLocale::system()返回一個QLocale物件,以提供使用者的locale資訊。

    QTranslator物件只能一次載入一個translation file;通過使用多個QTranslator物件,Qt程式可以安裝任意數量的translator;

QApplication在尋找合適的翻譯時會使用所有已安裝的QTranslator。

    QTranslator::load()用於載入translation file (.qm)

    QApplication::setLayoutDirection(),該函式可以改變文字的書寫方向。

    QLocale類提供本地化的數字和日期/時間格式。

17.3    Dynamic Language Switching

    當Qt檢測到環境變數中的locale設定發生變化時,會建立一個LocalChange event;若要處理該event,需要重新實現QWidget::changeEvent()。

    當QApplication上已安裝的QTranslator的內容發生變化時,Qt會建立一個LanguageChange event。

    不要將LocalChange和LanguageChange兩個event混淆;前者的發生是由系統變化導致的,所通知的物件是Qt應用程式;後者的產生是Qt自身導致的,所通知的物件是程式的widgets。

17.4    Translating Applications

    翻譯的三個步驟:

    1).    開發人員執行lupdate程式從原始碼中提取使用者可見字串,並建立XML格式的.ts檔案

    2).    翻譯人員執行Qt linguist這個GUI工具,完成文字的翻譯工作

    3).    開發人員執行lrealease這個工具,生成應用程式可以通過QTranslator載入的二進位制.qm檔案

    lrelease的工作就是將可讀文字的.ts檔案轉換為二進位制的.qm檔案。

    lupdate預設情況下假設所有tr()中的字串都採用Latin-1編碼;若實際情況並非如此,需要在.pro檔案中新增CODECFORTR這一項;該工作和在程式中呼叫QTextCodec::setCodecForTr()二者是缺一不可的。

 

Chapter 18 Multithreading

18.1    Creaing Threads

Qt中提供多執行緒的機制很簡單:建立QThread的派生類,並重新實現其保護成員函式run()。

    QThread::run(),被呼叫來開始執行緒的執行,在run()結束時執行緒終止。

    QThread::terminate(),用來終止執行緒的執行,非阻塞操作,並不保證執行緒的立即終止;可以在呼叫QThread::terminate()之後呼叫QThread::wait()來實現同步等待。

    terminate()並不是值得推薦結束執行緒的方法,因為它強制執行緒終止而不給執行緒任何清場的機會。

18.2    Synchronizing Threads

    Qt中用於執行緒同步的類包括QMutex,QReadWriteLock,QSemaphore和QWaitCondition

QMutex

    QMutex::lock()            阻塞操作

    QMutex::trylock()        非阻塞操作

    QMutex::unlock()   

    QMutexLocker是Qt提供的用於簡化Mutex操作的一個類——QMutexLocker的建構函式以一個QMutex物件為引數,並對其自動執行lock操作;而在解構函式則對其自動執行unlock操作。

QReadWriteLock可以允許同時進行多個讀操作或一個寫操作。

    QReadWriteLock::lockForRead()

    QReadWriteLock::lockForWrite()

    QReadWriteLock::unlock()

QSemaphore是對Mutex的擴充套件;與讀寫鎖不同的是,訊號量可以用來保護一批相同的資源,而不只是一個。

    QSemaphore::acquire(int n=1)

    QSemaphore::release(int n=1)

    QSemaphore::available()

QWaitCondition和QMutex聯合使用,可以允許一個執行緒在某個條件滿足時喚醒其他執行緒,比起單獨使用QMutex能實現更精確的控制。

    QWaitCondition::wait()的引數是一個狀態為locked的QMutex,該函式在阻塞本執行緒前會將這個QMutex解鎖,並在函式返回前對其lock。

TLS(thread-local storage)

    較好的實現方法是使用QThreadStorage<T>類,該類常用來實現cache,這樣可以避免使用mutex時lock,unlock以及等待帶來的開銷。

    由於某些編譯器的問題,QThreadStorage<T>中只能存放指標。

    QThreadStorage::hasLocalData()

    QThreadStorage::setLocalData()

18.3    Communicating with MainThread

    當Qt程式執行時,主執行緒是唯一的執行緒,並且是唯一允許建立QApplication或QCoreApplication物件並對其呼叫exec()的執行緒。在呼叫exec()之後,主執行緒要麼是在等待event的發生,要麼是在處理一個event。

    主執行緒可以通過建立QThread的子類來開始新執行緒。

    之前介紹的mutex,read/write lock,semaphore等均可用於新執行緒之間的通訊,但是卻不能用於和主執行緒的通訊,因為這會導致主迴圈的event loop被阻塞並"凍結"UI。

    解決方案是在主執行緒與新執行緒之間跨執行緒的使用signal-slot機制。

    通常情況下signal-slot機制是同步工作的,這意味著當signal被emit時,與之想聯絡的slot會被立即呼叫。

    然而,當該機制用於將不同執行緒中的object連線起來時,則變為非同步機制。這樣的連線是在底層是通過建立並傳遞event來實現的;slot被signal的接收物件所在的執行緒的event loop所呼叫。

    預設情況下,一個QObject物件存在於其被建立的執行緒之中;這可以在任何時候呼叫QObject::moveToThread()被改變。

18.4    Using Qt's Classess in Secondary Threads

    Thread-safe & Reentrant    注意留意這兩個概念應用在函式和類之上的不同。

    對於類,如果它的所有成員函式都可以被不同執行緒同時呼叫而不相互影響——即使這些呼叫是針對同一個類物件,那麼該類被定義為thread-safe。

    對於類,如果其不同例項可以在不同執行緒中被同時使用而不相互影響,那麼該類被定義為reentrant;然而,不同執行緒中同時訪問同一個reentrant類物件,並不是安全的,這樣的訪問需要用mutex進行保護。

    在Qt的定義中,在類這個層次,thread-safe是比reentrant更嚴格的要求,這和在函式層次上的關係正好相反。

    通常情況下C++的類只要不使用全域性或其它共享變數,就是reentrant的。

    Qt中大多數non-GUI的類,是屬於reentrant的。QObject是reentrant的,但是需要注意以下幾點:

    1).    子QObject必須在父QObject所屬的執行緒中被建立,這意味著在非主執行緒中的物件在建立時不允許以QThread作為parent,因為後者是在主執行緒或另外一個非主執行緒中被建立的。

    2).    在刪除一個QThread物件前,必須將對應執行緒中建立的所有物件都銷燬。

    3).    物件必須在其被建立的執行緒中被刪除。

     如果需要刪除存在於另一個執行緒中的物件,必須呼叫執行緒安全的QObject::deleteLater()函式,該函式會傳送一個"defered delete" event。

     QWidget及其子類不是reentrant的。

 

Chapter 19 Creating Plugins

Qt提供了Qlibrary類,用於以一種平臺無關的方式實現在程式執行時載入共享庫。

19.1    Extending Qt with Plugins

    Qt本身可以被很多型別的plugin擴充套件,最常見的包括database drivers,image formats,text codecs等。

    對於每種型別的plugin,通常都需要兩個類:一個wrapper類實現該類通用的plugin API,以及一個或多個handler類,每個類實現一個plugin特定的API。

    在plugin的.cpp檔案中,需要使用Q_EXPORT_PLUGIN()這個巨集來確保plugin能夠被Qt識別。

    plugin真正執行的操作都是通過其handler類來實現的。

    plugin的.pro檔案與應用程式不同。預設情況下.pro檔案使用app模板,然而這裡必須使用lib模板,因為plugin屬於庫,而不是一個獨立的應用程式。

    QCoreApplication::addLibraryPath( ),為程式新增新的庫路徑。

19.2    Making Application Plugin-Aware

    應用程式的plugin實際是實現了一個或多個介面(interface)的動態庫。應用程式與plugin之間的通訊是通過interface的virtual table來完成的。

    一個介面(interface)通常宣告一個virtual解構函式,一個返回QStringList的virtual函式,以及一個或多個其他virtual函式。

    在介面宣告的尾部,需要呼叫Q_DECLARE_INTREFACE2()來將該interface與某個識別符號關聯起來。

    QPluginLoader類用於在執行時載入plugin。

    QPluginLoader::load(),通常不需要顯式呼叫,因為instance()函式會在必要時呼叫該函式完成載入。

    QPluginLoader::instance(),返回一個指向plugin物件的QObject *指標。

    同一個外掛plugin可以成功cast至多個interface,因為plugin可以通過多重繼承來提供多個interface。

19.3    Writing Application Plugins

    應用程式的plugin是其要提供的interface和QObject二者的子類。

    在plugin的原始碼中,需要為其提供的每個Interface都要使用Q_INTERFACES()巨集,來保證moc和qobject_cast<T>之間的協調工作。

    在.cpp檔案的尾部,同樣需要呼叫Q_EXPORT_PLUGIN2()巨集來使該plugin對於Qt可用。

 

Chapter 20 Platform-Specific Features

20.1    Interfacing with Native APIs

在每個平臺上,Qt都為QWidget提供了一個winId()函式,返回window ID或是控制程式碼;QWidget還提供了一個靜態函式find(),返回一個特定window ID對應的widget。我們可以將獲得的window ID傳遞給Native API來執行平臺特定的操作。

    Qt定義了以下系統標誌:Q_WS_WIN,Q_WS_X11,Q_WS_MAC,Q_WS_QWS(Qtopia)。

    QSysInfo::WindowsVersion  QSysInfo::MacintoshVersion    這兩個靜態變數儲存著WIN和MAC作業系統的版本資訊

20.2    Using ActiveX on Windows

    ActiveX構建於Microst COM之上,它為使用元件的應用程式定義了一套介面,為提供元件的庫和應用程式定義了另一套介面。

    ActiveQt由兩個模組組成:

        QAxContainer模組允許使用者使用COM object並在Qt程式中內嵌ActiveX控制元件。

        QAxServer模組允許使用者匯出自定義的COM object以及用Qt編寫的ActiveX控制元件。

    Q_ENUMS()巨集的作用是告知moc其"巨集引數"是列舉型別。

    QAxContainer模組由三個類組成:QAXObject封裝一個COM object,QAxWidget封裝一個ActiveX控制元件,QAxBase為QAxObject和QAxWidget實現核心COM功能。

    QAxObject派生自QAxBase和QObject,QAxWidget派生自QAxBase和QWidget。

    COM中的資料型別會被自動轉換為合適的Qt資料型別。

    QAxBase::setControl()

    QObject::setProperty()可用於設定COM property和Qt property。

    要連結QAxContainer庫的話,需要在.pro檔案中新增下列一行:"CONFIG +=qaxcontainer"

    QAxBase::dynamicCall()

    注意,QAxObject和QAxWidget的子類無法定義新的property,signal和slot。

    QAxServer模組允許將一個標準Qt程式轉換為一個ActiveX server。該server可以是共享庫,也可以是獨立的應用程式。共享庫形式的server被稱為in-process servers,而獨立應用程式形式的server被稱為out-of-process server。

    QAxBindable在widget與ActiveX client之間提供了一個介面。

    在Qt中處理多重繼承中,如果基類中存在QObject的派生類,必須將這樣的類放在首位。

    QAXFACTORY_DEFAULT()巨集的作用是匯出一個AxtiveX控制元件,可以用於僅匯出一個控制元件的ActiveX server;當server要匯出多個控制元件時,不能使用QAXFACTORY_DEFAULT()巨集。

    QApplication能夠識別命令列中的-activex引數,並使應用程式作為server而執行。

    Q_CLASSINFO()巨集

20.3    Handling X11 Session Management

    為了使一個Qt/X11應用程式意識到session manager的存在,需要重新實現QApplication::saveState()函式,並在該函式中儲存應用程式的狀態資訊。

    當使用者啟動shutdown操作時,程式設計師可以通過重新實現QApplication::commitData()來獲取控制權,這允許應用程式儲存未儲存的資料,並且與使用者互動——如果可能的話;這部分session management在X11和Windows上都被支援。

    QObject::setObjectName()

    void QApplicatoin::saveState(QSessionManager &)——該函式在session manager希望應用程式儲存其狀態時被呼叫,QSessionManager型別的引數允許應用程式與session manager進行通訊。

    discard command:是指session manager必須執行的用刪除任何儲存當前狀態資訊的命令。

    restart command:是指session manager必須執行的用以重新啟動應用程式的命令。

    QSessionManager::setDiscardCommand(QStringList &)

    QSessionManager::setDiscardCommand(QStringList &)

    預設情況下,Qt提供的restart command的格式為: appname -session id_key

    QSessionManager::release()

    QSessionManager::cancel()

    QApplication:isSessionRestored()